我对 Haskell 中的类感到困惑,如下所示。
我可以定义一个接受 Integral 参数的函数,并成功地为它提供一个 Num 参数:
gi :: Integral a => a -> a
gi i = i
gin = gi (3 :: Num a => a)
Run Code Online (Sandbox Code Playgroud)
我可以定义一个接受 Num 参数的函数,并成功地为它提供一个 Integral 参数:
fn :: Num a => a -> a
fn n = n
fni = fn (3 :: Integral a => a)
Run Code Online (Sandbox Code Playgroud)
我可以定义一个 Integral 值并为其分配一个 Num
i :: Integral a => a
i = (3 :: Num a => a)
Run Code Online (Sandbox Code Playgroud)
但是,如果我尝试定义一个 Num 值,那么如果我为其分配一个 Integral 值,则会出现解析错误
- this doesn't work
n :: Num a => a
n = (3 :: Integral a => a)
Run Code Online (Sandbox Code Playgroud)
也许我对我的 OO 背景感到困惑。但是为什么函数变量似乎让您“双向”,即可以在“预期”超类时提供子类的值,并在需要子类时提供超类的值,而在值分配中您可以提供将超类分配给子类值,但不能将子类分配给超类值?
为了进行比较,在 OO 编程中,您通常可以将子值分配给父类型,但反之则不然。在 Haskell 中,第二对示例中的情况似乎相反。
前两个例子实际上没有什么关系之间的关系Num和Integral。
看看ginand的类型fni。我们一起做吧:
> :t gin
gin :: Integer
> :t fni
fni :: Integer
Run Code Online (Sandbox Code Playgroud)
这是怎么回事?这称为“类型默认”。
从技术上讲,任何像Haskell 中的3或5或 的数字文字42都有 type Num a => a。因此,如果您希望它只是一个该死的整数,则您必须始终编写42 :: Integer而不是仅编写42. 这非常不方便。
所以为了解决这个问题,Haskell 有一些规则,在某些特殊情况下规定了当类型出现泛型时要替换的具体类型。而在这两种情况下Num,并Integral默认类型Integer。
因此,当编译器看到3并将其用作 的参数时gi,编译器默认为Integer。就是这样。您的附加约束Num a没有进一步的影响,因为Integer实际上已经是 的实例Num。
另一方面,对于最后两个示例,不同之处在于您明确指定了类型签名。你不只是让编译器来决定,不!你特意说的n :: Num a => a。所以编译器不能再决定n :: Integer了。它必须是通用的。
并且由于它是通用的,并且被限制为Num,因此Integral类型不起作用,因为正如您正确指出的那样,Num它不是Integral.
您可以通过提供fni类型签名来验证这一点:
-- no longer works
fni :: Num a => a
fni = fn (3 :: Integral a => a)
Run Code Online (Sandbox Code Playgroud)
等等,但不应该n仍然工作吗?毕竟,在 OO 中这会正常工作。以 C# 为例:
> :t gin
gin :: Integer
> :t fni
fni :: Integer
Run Code Online (Sandbox Code Playgroud)
啊,但这不是泛型!在上面的例子中,a是一个具体类型的值Num,而在你的 Haskell 代码a中,它本身就是一个类型,但被限制为Num. 这更像是 C# 接口而不是 C# 类。
而泛型类型(无论是否在 Haskell 中)实际上是相反的!取这样的值:
x :: a
x = ...
Run Code Online (Sandbox Code Playgroud)
这个类型签名说的是“谁有需要x,就来拿吧!但首先命名一个类型a。然后值x将是该类型。无论你命名哪种类型,这就是什么x”
或者,更简单地说,选择泛型类型的是函数的调用者(或值的使用者),而不是实现者。
因此,如果您这么说n :: Num a => a,则意味着该值n必须能够“变形”为任何类型a,只要该类型具有Num实例。谁将n在他们的计算中使用——那个人将选择是什么a。您, 的实施者n,不能选择那个。
并且由于您无法选择是什么a,因此您无法将范围缩小到不仅仅是 any Num,而是Integral. 因为,您知道,有些Nums 不是Integrals,所以如果使用的人n选择其中一种非Integral类型作为s,您将怎么办a?
在i这种情况下工作正常,因为 everyIntegral也必须是Num,所以无论消费者i选择什么a,您肯定知道它将是Num。