Question

Extending types in a backward compatible way?

Let's say I have:

data X = X Int Char Whatever

Now let's say I want to add a type parameter to X like so:

data Version = OldVersion | NewVersion

data X (phantomParam :: Version) = X Int Char Whatever

I'm doing this because internally, I'd like to vary this parameter to change the behavior of some instances.

But I'd like any code I don't touch to still work.

So my first thought was to do the following:

data X' (phantomParam :: Version) = X Int Char Whatever -- note the dash

type X = X' OldVersion

Then all existing code should be unchanged. Which almost works, except now when I export:

X(X)

I get an error.

That's not a big deal, because I can export:

X, X'(X)

But now I have to also change all imports from:

X(X)

to

X, X'(X)

presumably breaking all code using this. Is there any way to do this refactoring without breaking downstream code? I'm thinking pattern synonyms might be helpful here but I'm not exactly able to get this to work.

 7  83  7
1 Jan 1970

Solution

 2

Unfortunately I'm pretty sure there's no good way to do this. The best you can do is make a new type, plus expose conversions for code that's multi-version aware but that needs to interface with unaware code.

data X = X Int Char Whatever
data X' param where
    X' :: X -> X' OldVersion
    Y :: Whatever' -> X' NewVersion

toLegacy :: X' OldVersion -> X
toLegacy (X' x) = x -- this pattern match is actually total

fromLegacy :: X -> X' OldVersion
fromLegacy = X' -- okay, this probably isn't needed
2024-07-23
Daniel Wagner