I’ve got the following record defined:
data Option = Option {
a :: Maybe String,
b :: Either String Int
} deriving (Show)
Is there anyway for me to enforce that when a is Nothing, b must be a Left and when a is Just, b must be a Right? Maybe with phantom types, or something else? Or must I wrap the whole thing inside of an Either and make it Either String (String, Int) ?
You should just use two constructors for the two possible shapes:
Of course, you should give them better names, based on what they represent. Phantom types are definitely overkill here, and I would suggest avoiding
Either—LeftandRightare not very self-documenting constructor names.If it makes sense to interpret both Either branches of the b field as representing the same data, then you should define a function that reflects this interpretation:
If you have fields that stay the same no matter what the choice, you should make a new data type with all of them, and include it in both constructors. If you make each constructor a record, you can give the common field the same name in every constructor, so that you can extract it from any
Optionvalue without having to pattern-match on it.Basically, think about what it means for the string not to be present: what does it change about the other fields, and what stays the same? Whatever changes should go in the respective constructors; whatever stays the same should be factored out into its own type. (This is a good design principle in general!)
If you come from an OOP background, you can think about this in terms of reasoning with composition instead of inheritance — but try not to take the analogy too far.