【问题标题】:One value constructor belonging to two different types属于两种不同类型的一个值构造函数
【发布时间】:2010-11-25 12:12:24
【问题描述】:

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

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

我想创建XY 两种类型,这样X 类型的值可以是ABC,如下所示:

data X = A {...} | B {...} | C {...}

Y 类型的值只能是AB,如下所示:

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

我知道我可以将构造函数包装在 XY 的定义中,如下所示:

data X = XA A | XB B | XC C
data Y = YA A | YB B

但这似乎不整洁(必须一直输入XA A 等)。我可以将ABC的内容扩展为XY的定义,但是A等非常复杂,我不想重复定义。

这对 Haskell 是否可行,包括任何 GHC 扩展?

编辑

GADT 似乎可以按要求回答我的问题(因此我将散热器的回答标记为正确),但仍然不够灵活,无法满足我的需要。例如,据我所知,您不能执行以下操作:

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 应该输入为[XY X_],但这在 Haskell 中是不可能的(除非我的实验是错误的)。

经过更多的网络搜索,我真正想要的是 OCaml 的多态变体(据我所知)仅存在于 OCaml 中(着眼于更“实用”而不是“学术”语言)。

编辑 2

查看comonad的回答。看来真的可以,不过我觉得这个问题最好不要重写太多次。 :-)

【问题讨论】:

  • 您对foobar 的定义不会进行类型检查,因为根据定义A _A 类型的值,而不是XY。不能有其他类型 (X) 具有相同的构造函数。
  • 另一种看待它的方式:A 有什么类型?只能有一个,所以不能同时是Int -> XInt -> Y
  • jetxee 和 Antal S-Z:我知道我对 foobar 的定义不会进行类型检查,但我希望类似的东西是有效的 Haskell。例如,对于类型类,read x(其中xString)可以(在某种意义上)具有多个类型。

标签: haskell types


【解决方案1】:

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

【讨论】:

  • 谢谢。我会看看 GADT,看看它们是否是我需要的。
【解决方案2】:
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) 才能获得包含 AX 类型的值。这不是您想要的,但恐怕是您最接近的。

【讨论】:

  • 确实,通过将对象“定义”为唯一的一种“类型”,而不是类。或者,如果您更喜欢类型集是 Haskell 中所有“对象”集的一个分区
  • 嗯...是的,我想我希望得到比这更好的东西。也许 GADT(另一个答案表明这一点)将是我正在寻找的。​​span>
【解决方案3】:

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

data X = XA A | XB B | XC C
data Y = YA A | YB B

但是让我们从另一个角度来处理它。 为什么你需要那个?你想表达的是,X 可以是ABC,而Y 可以是AB。您不关心 ABC 的值。所以成为A成为BXY共同的特征。

当您在两种类型(本例中为XY)之间具有共同特征时,您通常可以使用类型类来表达它。请注意,类型类是开放的,因此可以根据需要实现任意数量的类型。

例如,我们可以定义三个类型类来检查类型是否有ABC

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

但我们可以为XY 定义类实例:

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

使用这些类型类,您可以编写foobar,它们同时接受Xs 和Ys。

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 接受实现HasAHasBHasCanything,而bar 接受实现HasAanythingHasB(在bar的上下文中是否实现HasC是无关紧要的)。如果实现碰巧在每种情况下都返回False,那么foobar 是未定义的。

例如:

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]

请注意bar 也接受Xs,但如果它碰巧是除AB 之外的其他东西,则它是未定义的。在这种情况下,作为程序员,而不是编译器,您有责任考虑警卫的详尽性。

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

class HasA t where getA :: t -> Maybe A

但想法是一样的。

【讨论】:

  • 谢谢。我已经想到了这一点,但认为我无法对满足类型类(HasA)的各种类型(XA,YA)进行模式匹配,但我没有考虑守卫。但是我确实需要 A、B 和 C 的值,而且这个解决方案可能与最初的 XA 和 YA 想法一样冗长(如果不是更多的话)。
【解决方案4】:

使用散热器的答案,我想出了这个:

{-# 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]

的限制被删除。现在它可以工作了,但是类型中的 a 看起来有点奇怪。

type Y = forall a. XY a -- 不起作用。

【讨论】:

    猜你喜欢
    • 2022-01-25
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 2019-01-03
    • 2016-01-05
    • 2020-12-11
    • 2014-03-12
    • 1970-01-01
    相关资源
    最近更新 更多