One value constructor belonging to two different types
Let's say I have three value constructors:
A { a :: Int }
B { b :: Char }
C { c :: Bool }
I would like to create tw开发者_如何学运维o 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.
data X_
data Y_
data XY a where
A :: Int -> XY a
B :: Char -> XY a
C :: Bool -> XY X_
type X = XY X_ -- Contains values built with constructors A, B, and C
type Y = XY Y_ -- Contains only values built with constructors A and B
Now a function that uses only A
and B
works with both types X
and Y
. A function that uses C
will work only with type X
.
baz = A 1 -- baz is inferred to be a type that can fit in both X and Y
This would require Haskell to support some form of subtyping, which it does not. There are no ghc extensions that enable this either.
The best thing you can do is probably something like this:
data Y = A ... | B ...
data X = XY Y | C ...
This way you don't have to repeat the constructors A
and B
and you don't have to write Y (A foo)
either - you can just write A foo
to get a value of type Y
.
However you will have to write X (A foo)
to get a value of type X
which contains an A
. This isn't quite what you want, but the closest you will get, I'm afraid.
Your definitions for foo
and bar
won't type check, because A _
is by definition a value of type A
, not X
or Y
. You cannot have another type (X
) with the same constructor. So, the correct thing is what you've written:
data X = XA A | XB B | XC C
data Y = YA A | YB B
But let's approach it from another perspective. Why do you need that? You want to express, that X
can be either, A
, B
, or C
, and Y
can be either A
or B
. You don't care about values of A
, B
and C
respectively. So being A
and being B
are features common to both X
and Y
.
When you have a common trait beteen two types (X
and Y
in this case), you can often express it with type classes. Please note that type classes are open, so as many types can implement them as you like.
For example, we can define three type classes which allow to check if the type has A
, B
or C
:
class HasA t where hasA :: t -> Bool
class HasB t where hasB :: t -> Bool
class HasC t where hasC :: t -> Bool
Now for our types we still have to use distinct data constructors:
data A = A Int
data B = B Char
data C = C Bool
data X = XA A | XB B | XC C
data Y = YA A | YB B
But we can define class instances both for X
and for Y
:
instance HasA X where
hasA (XA _) = True
hasA _ = False
instance HasB X where
hasB (XB _) = True
hasB _ = False
instance HasC X where
hasC (XC _) = True
hasC _ = False
instance HasA Y where
hasA (YA _) = True
hasA _ = False
instance HasB Y where
hasB (YB _) = True
hasB _ = False
instance HasC Y where
hasC = const False
With these type classes you can write foo
and bar
which accept both X
s and Y
s.
foo :: (HasA t, HasB t, HasC t) => t -> Int
foo v | hasA v = 1
| hasB v = 2
| hasC v = 3
| otherwise = undefined
bar :: (HasA t, HasB t) => t -> Bool
bar v | hasA v = True
| hasB v = False
| otherwise = undefined
xs = [ XA (A 1), XB (B '1'), XC (C True) ]
ys = [ YA (A 1), YB (B '1') ]
More precisely, foo
accepts anything which implements HasA
, HasB
and HasC
, and bar
accepts anything which implements HasA
and HasB
(whether or not HasC
is implemented is irrelevant in the context of bar
). If the implementation happens to return False
in every case, then foo
and bar
are undefined.
For example:
ghci> map foo xs
[1,2,3]
ghci> map foo ys
[1,2]
ghci> map bar xs
[True,False,*** Exception: Prelude.undefined
ghci> map bar ys
[True,False]
Please note that bar
accepts also X
s, but it is undefined if it happens to be something else except A
or B
. You as a programmer, not the compiler, are responsible to think about exhaustiveness of the guards in this case.
If you need also the values of A
, B
and C
, then you have to design the type classes differently, e.g. like
class HasA t where getA :: t -> Maybe A
but the idea is the same.
Using Heatsink's answer, I kame up with this:
{-# LANGUAGE GADTs,EmptyDataDecls #-}
module Test where
data NotThatY
data XY a where
A :: Int -> XY a
B :: Char -> XY a
C :: Bool -> XY NotThatY
type Y a = XY a
type X a = XY NotThatY -- or type X =..., but (X a) looks better alongside (Y a).
func1 :: [Y a]
func1 = [A 5, B 'ö']
func2 :: [X a]
func2 = func1 ++ [C True]
The restriction to Y is removed. Now it works, but it looks somehow strange with that a in the type.
type Y = forall a. XY a
-- does not work.
精彩评论