Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending algebraic data type

Note: if this question is somehow odd, this is because I was only recently exposed to Haskell and am still adapting to the functional mindset.

Considering a data type like Maybe:

data MyOwnMaybe a = MyOwnNothing | MyOwnJust a

everyone using my data type will write functions like

maybeToList :: MyOwnMaybe a -> [a]
maybeToList MyOwnNothing  = []
maybeToList (MyOwnJust x) = [x]

Now, suppose that, at a later time, I wish to extend this data type

data MyOwnMaybe a = MyOwnNothing | MyOwnJust a | SuperpositionOfNothingAndJust a

how do I make sure that everyone's functions will break at compile-time?

Of course, there is the chance that somehow I'm not "getting" algebraic data types and maybe I shouldn't be doing this at all, but considering a data type Action

data Action = Reset | Send | Remove

it would seem that adding an extra Action like Add would not be so uncommon (and I wouldn't want to risk having all these functions around that possibly cannot handle my new Action)

like image 466
Werner de Groot Avatar asked Mar 14 '23 22:03

Werner de Groot


1 Answers

Well, bad news first: sometimes you just can't do it. Period.

But that is language-agnostic; in any language you sometimes have to break interface. There is no way around it.

Now, good news: you can actually go a great length before you have to do that.

You just have to carefully consider what you export from your module. If, instead of exporting the internal workings of it, you export high-level functions, then there is a good chance you can rewrite those function using the new data type, and everything would go smooth.

In particular, be very careful when exporting data constructors. In this case, you don't just export functions that create your data; you are also exporting the possibility of pattern-matching; and that is not something that ties you pretty tight.

So, in your example, if you write functions like

myOwnNothing :: MyOwnMaybe a
myOwnJust :: a -> MyOwnMaybe a

and

fromMyOwnMaybe :: MyOwnMaybe a -> b -> (a -> b) -> b
fromMyOwnMaybe MyOwnNothing b _ = b
fromMyOwnMaybe (MyOwnJust a) _ f = f a

then it's reasonable to assume that you would be able to reimplement it for the updated MyOwnMaybe data type; so, just export those functions and the data type itself, but don't export constructors.

The only situation in which you would benefit from exporting constructors is when you are absolutely sure that your data type won't ever change. For example, Bool would always have only two (fully defined) values: True and False, it won't be extended by some FileNotFound or anything (although Edward Kmett might disagree). Ditto Maybe or [].

But the idea is more general: stay as high-level as you can.

like image 150
MigMit Avatar answered Mar 24 '23 05:03

MigMit