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:
data Option = NoA String | WithA String Int
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
— Left
and Right
are 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:
b :: Option -> MeaningOfB
b (NoA s) = ...
b (WithA t n) = ...
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 Option
value 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With