Let’s say I have three value constructors:
A { a :: Int }
B { b :: Char }
C { c :: Bool }
I would like to create two types X and Y such that a value of type X can be an A, B or C, something like this:
data X = A {...} | B {...} | C {...}
and a value of type Y can be only an A or B, something like this:
data Y = A {...} | B {...}
so that I can code something like this:
foo :: X -> Int -- can pattern match
foo (A _) = 1
foo (B _) = 2
foo (C _) = 3
bar :: Y -> Bool -- also can pattern match with the same constructors
bar (A _) = true
bar (B _) = false
baz = A 1 -- baz is inferred to be a type that can fit in both X and Y
I know that I can wrap the constructors in the definitions of X and Y like this:
data X = XA A | XB B | XC C
data Y = YA A | YB B
but this seems untidy (having to type XA A etc. all the time). I could expand the contents of A, B, and C into the definitions of X and Y, but A etc. are quite complicated and I would prefer not to duplicate the definition.
Is this possible with Haskell, including any GHC extensions?
Edit
It seems that GADTs can answer my question as asked (so I marked heatsink’s answer as correct), but still aren’t flexible enough for what I need. For example, as far as I know, you can’t do something like:
func1 :: [XY Y_] -- returns a list of items that can only be A or B
func1 = ...
func2 = func1 ++ [C True] -- adding a C item to the list
func2 should be typed as [XY X_], but that isn’t possible in Haskell (unless my experiment was wrong).
After more web searching, what I really want is OCaml’s polymorphic variants which (as far as I know) exist only in OCaml (looking at the more “practical” as opposed to “academic” languages).
Edit 2
See comonad’s answer. It seems that it really can be done, but I think I’d better not rewrite this question too many times. 🙂
Type classes, as jetxee described, are probably the appropriate approach.
If you also want the ability to pattern match and use constructors, then you can define all the constructors within one data type using GADTs and empty data declarations. If you take this approach, all the constructors will be members of the same data type, while letting you restrict the domain to only a subset of the constructors.
Now a function that uses only
AandBworks with both typesXandY. A function that usesCwill work only with typeX.