Search code examples
haskellrecord

Haskell "dependent" fields of a record?


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) ?


Solution

  • 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 EitherLeft 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.