为什么这个函数签名无法通过类型检查?

4
为什么extractEither无法通过类型检查?
data MyEither a b = MyLeft a | MyRight b
                    deriving (Read, Show)

extractEither :: MyEither a b -> c
extractEither (MyLeft p) = p

编译器显示:
Couldn't match type `a' with `c'
  `a' is a rigid type variable bound by
      the type signature for extractEither :: MyEither a b -> c
      at /Users/tongmuchenxuan/playground/test.hs:5:1
  `c' is a rigid type variable bound by
      the type signature for extractEither :: MyEither a b -> c
      at /Users/tongmuchenxuan/playground/test.hs:5:1
In the expression: p
In an equation for `extractEither': extractEither (MyLeft p) = p

C语言中的`c'不是足够通用,可以捕捉任何类型吗?

4个回答

12
不,c是“调用者希望函数返回的任何类型”,即对于一个函数f :: a -> c,无论是f x + 1 :: Int,还是putStr $ f x,以及main = f x,都需要可以实现。但你的函数显然不允许这样做。
您想要的是返回动态类型。Haskell故意不像其他一些语言那样轻松地允许这样做,因为当返回类型是意外类型时,它明显会很容易导致运行时错误。有各种方法可以实现它,但选择哪种取决于您实际需要它的用途。您能提供一些上下文吗?最好的解决方案可能并不使用动态类型,而是使用更符合Haskell习惯的东西。
也许您想要的只是:
extractEither :: MyEither a a -> a
extractEither (MyLeft p) = p
extractEither (MyRight p) = p

这需要"两边"的类型相同。


谢谢。你们两位的回答都很清晰,解决了我的难题! - M. Tong
实际上,这个问题并不预设动态类型,而只是子类型和向上转型,据我所知可以进行静态检查。然而,Haskell却没有这些功能。 - Luis Casillas

11

基本上,你的类型签名表示你的函数可以针对每个 ab 返回任何类型的值 c,但实际上,根据编译器的提示,此处不可能产生任何这样的 c 值,因为您实际上返回了一个类型为 a 的值(即 p :: a)。

此外,您的定义仍未处理 MyEither a b 不是 Left a 的情况。例如,当您调用 extractEither(MyRight 1) 时,它应该做什么?如果您尝试运行这样的函数(当然,在更正类型签名之后),您可能会遇到所谓的“非全面模式”异常,这意味着没有 extractEither 的主体定义来处理一些可能的输入模式。

如果您正在尝试编写一个单个函数,既适用于提取 MyLeft 值,也适用于提取 MyRight 值,那么我恐怕您应该改变您的想法。为了提取任何左侧或右侧的值,您应该在两侧具有相同的类型(即 MyEither a a),这并不是很有用。

您应该考虑编写以下功能来提取值。在这里,我返回 Maybe 以避免在尝试从右值中提取左值时管理调用 error 的负担:

extractMyLeft :: MyEither a b -> Maybe a
extractMyRight :: MyEither a b -> Maybe b

编辑: 另外,请参考dblhelix的答案,他提出了另一种可能有用的重构方式,可以几乎获得您所寻找的类型签名。


8
除了Riccardo的回答之外:如果你想要一个函数,能够从MyEither值中提取出某种类型c的值,无论它是由MyLeft还是MyRight构建的,你可以让该函数不要求该值具有MyEither c c类型,而是“指导”该函数如何从任一侧获取一个以c为类型的值。
extractEither :: (a -> c) -> (b -> c) -> MyEither a b -> c
extractEither f g (MyLeft x)  = f x
extractEither f g (MyRight y) = g y

也就是说,extractEither现在需要两个额外的参数fg:这些函数用于将提取的值转换为所需类型的值。
这是处理和求和类型一起工作时非常常见和有用的习惯用法。

我会在我的回答中添加一行来引用你的。+1 :) - Riccardo T.
谢谢。但在这种情况下,我必须指定f和g,对吗?有没有一种方法只指定其中一个并说“我不关心另一个是什么”? 就像匹配模式中的_一样。 - M. Tong
是的,你可以:extractEither undefined id (MyRight 3.14),但是你最好确定不要提供使用 MyLeft 构建的值。 ;) - Stefan Holdermans
值得注意的是,extractEitherData.Either 中被定义为 either - Tilo Wiklund
2
值得注意的是,它的类型对应于逻辑析取的消除规则:“如果‘a’成立,则如果‘b’成立,则如果‘a’或‘b’成立,则‘c’成立。”具有终止函数的通用类型对应于逻辑重言式。与问题中的原始类型进行比较:“如果‘a’或‘b’,则‘c’”(例如,“如果乔治·华盛顿是美国第一任总统或斯大林格勒德战役始于1942年,则拿破仑·波拿巴射杀了约翰·F·肯尼迪”)。 - Luis Casillas

1
extractEither :: MyEither a b -> c
extractEither (MyLeft p) = p

“c”不是足够通用以捕获任何类型吗?
你所提出的直觉适用于面向对象语言或其他具有子类型的语言,但不适用于Haskell或其他参数化类型系统(例如Java泛型)。请看Java中的这个方法签名:
public Object blah(Foo whatever);

此方法返回Object,这是层次结构中最高的类型。这意味着该方法的实现可以选择返回任何类型,并且它将被轻松地向上转换为Object

Haskell类型不是这样工作的。你在想你的extractFilter可以返回p,因为你暗示地假设,也就是extractEither的作者,可以选择用于c的类型,就像blah的作者可以选择结果的运行时类型一样。但在Haskell中,情况正好相反:你的函数调用者可以选择用于abc的类型,并且他们可以选择不兼容的ac。如果我选择任何ac,那么你无法编写任何终止代码来将我的任意a转换成我的任意c


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接