为什么我不能在哈斯克尔中添加整数到双精度?



为什么我可以这样做:

1 + 2.0

但当我尝试:

let a = 1
let b = 2.0
a + b
<interactive>:1:5:
    Couldn't match expected type `Integer' with actual type `Double'
    In the second argument of `(+)', namely `b'
    In the expression: a + b
    In an equation for `it': it = a + b

这看起来很奇怪!它曾经绊倒过你吗?

注:我知道"1"one_answers"2.0"是多态常数。这不是我担心的。让我担心的是为什么haskell在第一种情况下做了一件的事情,而在第二种情况下又做了另一件 !

(+)的类型签名被定义为Num a => a -> a -> a,这意味着它适用于Num类型类的任何成员,但两个参数必须是相同的类型。

这里的问题是GHCI和它建立类型的顺序,而不是Haskell本身。如果你把你的任何一个例子放在一个文件中(使用do let表达式),它将编译和运行良好,因为GHC将使用整个函数作为上下文来确定字面量12.0的类型。

在第一种情况下发生的是GHCI猜测你输入的数字的类型。最精确的是Double,所以它只是假设另一个应该是Double并执行计算。然而,当您使用let表达式时,它只有一个数字可以计算,因此它决定1Integer, 2.0Double

编辑:GHCI并不是真的"猜测",它使用了Haskell报告中定义的非常具体的类型默认规则。你可以在这里阅读更多内容

第一个有效,因为数字字面值是多态的(它们被解释为fromInteger literal resp)。fromRational literal),所以在1 + 2.0中,您实际上有fromInteger 1 + fromRational 2,在没有其他约束的情况下,结果类型默认为Double

由于单态限制,第二个不能工作。如果没有类型签名而使用简单的模式绑定(name = expresion)绑定某些东西,则该实体被分配为单态类型。对于字面量1,我们有一个Num约束,因此,根据默认规则,它的类型在绑定let a = 1中默认为Integer。类似地,分数字面值的类型默认为Double

顺便说一下,如果在ghci中设置:set -XNoMonomorphismRestriction,它将工作。

单态限制的原因是为了防止共享的损失,如果你看到一个值看起来像一个常量,你不希望它被计算不止一次,但如果它是一个多态类型,它将被重新计算每次使用

您可以使用GHCI了解更多关于这方面的信息。使用:t命令获取表达式的类型。

Prelude> :t 1
1 :: Num a => a

所以1是一个常量,可以是任何数值类型(Double, Integer等)

Prelude> let a = 1
Prelude> :t a
a :: Integer

所以在这种情况下,Haskell推断出a的具体类型是Integer。类似地,如果您写let b = 2.0,那么Haskell推断类型为Double。使用let使Haskell推断出比(可能)必要的更具体的类型,这就导致了你的问题。(也许有比我更有经验的人可以解释一下为什么会这样。)因为(+)的类型是Num a => a -> a -> a,所以这两个参数需要具有相同的类型。

你可以用fromIntegral函数来解决这个问题:

Prelude> :t fromIntegral
fromIntegral :: (Num b, Integral a) => a -> b

该函数将整数类型转换为其他数字类型。例如:

Prelude> let a = 1
Prelude> let b = 2.0
Prelude> (fromIntegral a) + b
3.0

其他人已经很好地解决了这个问题的许多方面。我想说一下为什么+具有Num a => a -> a -> a类型签名的基本原理。

首先,Num类型类无法将Num的任意实例转换为另一个实例。假设我有一个虚数的数据类型;它们仍然是数字,但你真的不能正确地将它们转换成一个Int

其次,您喜欢哪种类型的签名?

(+) :: (Num a, Num b) => a -> b -> a
(+) :: (Num a, Num b) => a -> b -> b
(+) :: (Num a, Num b, Num c) => a -> b -> c

在考虑了其他选项之后,您意识到a -> a -> a是最简单的选择。多态结果(如上面的第三个建议)很酷,但有时可能过于通用而无法方便地使用。

第三,Haskell不是Blub。大多数关于Haskell的设计决策都没有考虑到流行语言的惯例和期望,尽管有争议的不是全部。我经常喜欢说,学习Haskell的第一步是首先忘掉你认为你知道的关于编程的一切。我相信大多数(如果不是全部的话)有经验的Haskellers都被Num类型类和Haskell的其他各种好奇心绊倒了,因为大多数人都先学习了一种更"主流"的语言。但是要有耐心,你最终会到达Haskell的天堂。:)

最新更新