【问题标题】:Confusion with Haskell classes与 Haskell 类混淆
【发布时间】:2021-05-28 20:33:07
【问题描述】:

我对 Haskell 中的类感到困惑,如下所示。

我可以定义一个接受 Integral 参数的函数,并成功地为其提供 Num 参数:

gi :: Integral a => a -> a
gi i = i
gin = gi (3 :: Num a => a)

我可以定义一个接受 Num 参数的函数,并成功地为其提供一个 Integral 参数:

fn :: Num a => a -> a
fn n = n
fni = fn (3 :: Integral a => a)

我可以定义一个 Integral 值并为其分配一个 Num

i :: Integral a => a
i = (3 :: Num a => a)

但是,如果我尝试定义一个 Num 值,那么如果我为其分配一个 Integral 值,则会出现解析错误

- this doesn't work
n :: Num a => a
n = (3 :: Integral a => a)

也许我对我的 OO 背景感到困惑。但是为什么函数变量似乎让你“双向”,即可以在“预期”超类时提供子类的值,并且可以在预期子类时提供超类的值,而在值赋值中你可以提供将超类赋值给子类值,但不能将子类赋值给超类值?

作为比较,在 OO 编程中,您通常可以将子值分配给父类型,但反之则不行。在 Haskell 中,第二对示例中的情况似乎正好相反。

【问题讨论】:

  • 您是否真的收到了 parse 错误,或者您收到的错误是 Could not deduce (Integral a) arising from an expression type signature from the context: Num a
  • 当你创建ginfni,然后用:t检查它们的类型,你看到了什么?

标签: haskell


【解决方案1】:

前两个示例实际上与NumIntegral 之间的关系没有任何关系。

看看ginfni的类型。让我们一起做吧:

> :t gin
gin :: Integer

> :t fni
fni :: Integer

发生了什么事?这称为“类型默认”。

从技术上讲,任何像 3542 在 Haskell 中的数字文字都有 Num a => a 类型。因此,如果您希望它只是一个整数,该死的,您必须始终编写 42 :: Integer 而不仅仅是 42。这非常不方便。

因此,为了解决这个问题,Haskell 有一些规则,在某些特殊情况下,当类型出现泛型时,指定要替换的具体类型。对于NumIntegral,默认类型为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)

等一下,n 不应该还能工作吗?毕竟,在 OO 中这可以正常工作。以 C# 为例:

class Num {}
class Integral : Num {}
class Integer : Integral {}

Num a = (Integer)3
// ^ this is valid (modulo pseudocode), because `Integer` is a subclass of `Num`

啊,但这不是泛型类型!在上面的示例中,a 是具体类型 Num 的值,而在您的 Haskell 代码中,a 本身就是一个类型,但被限制为 Num。这更像是一个 C# 接口而不是 C# 类。

而泛型类型(无论是否在 Haskell 中)实际上正好相反!取这样的值:

x :: a
x = ...

这个类型签名的意思是“谁需要x,来吧!但首先命名一个类型a。然后值x将属于该类型。无论你命名哪个类型,这就是x 的样子”

或者,更简单地说,选择泛型类型的是函数的调用者(或值的消费者),而不是实现者。

因此,如果您说n :: Num a => a,则意味着值n 必须能够“变形”为任何类型a,只要该类型具有Num 实例。谁会在他们的计算中使用n——那个人会选择a是什么。你,n 的实现者,不要选择那个。

由于您无法选择a 是什么,因此您无法将其缩小为不仅仅是任何Num,而是Integral。因为,你知道,有一些Nums 不是Integrals,那么如果使用n 的人选择其中一种非Integral 类型作为a,你会怎么做?


如果是i,这很好用,因为每个Integral 也必须是Num,所以无论i 的消费者选择a,你肯定知道它会是@ 987654380@.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-26
    • 2012-12-01
    • 2013-12-26
    相关资源
    最近更新 更多