一个属于两种不同类型的值构造函数

时间:2010-11-23 13:30:27

标签: haskell types

假设我有三个值构造函数:

A { a :: Int }
B { b :: Char }
C { c :: Bool }

我想创建两种类型XY,以使类型X的值可以是AB或{{1这样的事情:

C

并且data X = A {...} | B {...} | C {...} 类型的值只能是YA,如下所示:

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 AAB的内容扩展为CX的定义,但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的回答。它似乎真的可以做到,但我想我最好不要多次重写这个问题。 : - )

4 个答案:

答案 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

现在,仅使用AB的函数适用于XY两种类型。使用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 ...

这样您就不必重复构造函数AB,也不必编写Y (A foo) - 您只需编写A foo即可获取Y类型的值。

但是,您必须编写X (A foo)来获取包含X的{​​{1}}类型的值。这不是你想要的,但是你会得到最接近的,我害怕。

答案 2 :(得分:2)

您对foobar的定义不会进行类型检查,因为根据定义,A _的值为A,而不是X或{ {1}}。您不能使用具有相同构造函数的其他类型(Y)。所以,正确的是你写的:

X

但是让我们从另一个角度来看待它。 为什么你需要吗?您想表达的是,data X = XA A | XB B | XC C data Y = YA A | YB B 可以是XABC可以是Y或{ {1}}。您并不关心ABA的值。因此,B C AB共有的功能。

当你有一个共同特征时,有两种类型(在这种情况下为XY),你通常可以用类型类来表达它。请注意,类型类是开放的,因此许多类型都可以根据需要实现它们。

例如,我们可以定义三个类型类,以便检查类型是否包含XYA

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

使用这些类型类,您可以编写Yinstance 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 ,同时接受foobar 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') ] fooHasA任何HasB接受实现HasCbar的任何(无论是否HasA实现都与HasB的上下文无关。如果实现在每种情况下都返回HasC,则barFalse未定义。

例如:

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,但如果它恰好是除barX之外的其他内容,则不会被定义。作为程序员,而不是编译器,你有责任在这种情况下考虑守卫的详尽性。

如果您还需要ABA的值,则必须以不同方式设计类型类,例如像

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 - 无效。