假设我有三个值构造函数:
A { a :: Int }
B { b :: Char }
C { c :: Bool }
我想创建两种类型X
和Y
,以使类型X
的值可以是A
,B
或{{1这样的事情:
C
并且data X = A {...} | B {...} | C {...}
类型的值只能是Y
或A
,如下所示:
B
这样我就可以编写这样的代码:
data Y = A {...} | B {...}
我知道我可以将构造函数包装在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
和X
的定义中,如下所示:
Y
但这似乎不整洁(必须一直输入data X = XA A | XB B | XC C
data Y = YA A | YB B
等)。我可以将XA A
,A
和B
的内容扩展为C
和X
的定义,但Y
等等。很复杂,我宁愿不复制定义。
这是否可以与Haskell一起使用,包括任何GHC扩展?
修改
似乎GADT可以按照要求回答我的问题(因此我将散热片的答案标记为正确),但仍然不够灵活,无法满足我的需求。例如,据我所知,你不能做类似的事情:
A
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
,但这在Haskell中是不可能的(除非我的实验是错误的)。
经过更多的网络搜索,我真正想要的是OCaml的多态变体(据我所知)仅存在于OCaml中(看起来更“实用”而不是“学术”语言)。
修改2
请参阅comonad的回答。它似乎真的可以做到,但我想我最好不要多次重写这个问题。 : - )
答案 0 :(得分:7)
类型类,如所描述的jetxee,可能是适当的方法。
如果您还希望能够模式匹配并使用构造函数,那么您可以使用GADT和空数据声明来定义一种数据类型中的所有构造函数。如果采用这种方法,所有构造函数都将是相同数据类型的成员,同时允许您将域限制为仅构造函数的子集。
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
现在,仅使用A
和B
的函数适用于X
和Y
两种类型。使用C
的函数仅适用于X
类型。
答案 1 :(得分:5)
baz = A 1 -- baz is inferred to be a type that can fit in both X and Y
这将要求Haskell支持某种形式的子类型,但它不支持。没有ghc扩展可以实现这一点。
你可以做的最好的事情可能是这样的:
data Y = A ... | B ...
data X = XY Y | C ...
这样您就不必重复构造函数A
和B
,也不必编写Y (A foo)
- 您只需编写A foo
即可获取Y
类型的值。
但是,您必须编写X (A foo)
来获取包含X
的{{1}}类型的值。这不是你想要的,但是你会得到最接近的,我害怕。
答案 2 :(得分:2)
您对foo
和bar
的定义不会进行类型检查,因为根据定义,A _
的值为A
,而不是X
或{ {1}}。您不能使用具有相同构造函数的其他类型(Y
)。所以,正确的是你写的:
X
但是让我们从另一个角度来看待它。 为什么你需要吗?您想表达的是,data X = XA A | XB B | XC C
data Y = YA A | YB B
可以是X
,A
或B
,C
可以是Y
或{ {1}}。您并不关心A
,B
和A
的值。因此,为B
且为C
是A
和B
共有的功能。
当你有一个共同特征时,有两种类型(在这种情况下为X
和Y
),你通常可以用类型类来表达它。请注意,类型类是开放的,因此许多类型都可以根据需要实现它们。
例如,我们可以定义三个类型类,以便检查类型是否包含X
,Y
或A
:
B
现在我们的类型仍然需要使用不同的数据构造函数:
C
但我们可以为class HasA t where hasA :: t -> Bool
class HasB t where hasB :: t -> Bool
class HasC t where hasC :: t -> Bool
和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
定义类实例:
X
使用这些类型类,您可以编写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
,同时接受foo
和bar
s。
X
更确切地说,Y
接受实现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') ]
,foo
和HasA
的任何,HasB
接受实现HasC
和bar
的任何(无论是否HasA
实现都与HasB
的上下文无关。如果实现在每种情况下都返回HasC
,则bar
和False
未定义。
例如:
foo
请注意bar
也接受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]
s,但如果它恰好是除bar
或X
之外的其他内容,则不会被定义。作为程序员,而不是编译器,你有责任在这种情况下考虑守卫的详尽性。
如果您还需要A
,B
和A
的值,则必须以不同方式设计类型类,例如像
B
但想法是一样的。
答案 3 :(得分:1)
使用Heatsink的回答,我对此表示赞同:
{-# 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]
删除对 Y 的限制。现在它可以工作,但它在类型中的 a 看起来有些奇怪。
type Y = forall a. XY a
- 无效。