为什么我无法在Haskell中将Integer添加到Double中?

问题描述:

为什么,我可以做:为什么我无法在Haskell中将Integer添加到Double中?

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 

这似乎只是普通的怪异!它有没有让你起床?

P.S .:我知道“1”和“2.0”是多态常量。这不是我担心的事情。我担心的是为什么Haskell 一个事情在第一种情况下,但另一个在第二!

+3

由于meme去,“需要更多'fromIntegral'”。 –

+1

有点让我想起'x.f()'vs'g = x.f; g();'在Javascript中。当变量破坏替代模型抽象时,它就会变得很糟糕。 – hugomg

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

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

所以1是一个常数,其可以是任何数值类型(DoubleInteger等)

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

因此,在这种情况下,Haskell的推断具体类型为aInteger。同样,如果你写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 
+3

[单态限制](http://www.haskell.org/haskellwiki/Monomorphism_restriction)是'let'导致选择特定类型的原因。您可以通过给它一个类型签名或者通过在GHCi中执行':set -XNoMonomorphismRestriction'(或编译时的等效标志)来覆盖它。 – hammar

+0

编译时是否需要设置标志?我认为编译器不会“默认”... – drozzy

+0

@drozzy:它的确如此。 GHCi的违约规则略有不同。最值得注意的是,GHCi允许一组更大的类约束被默认,并且它将'()'添加到默认类型列表中。 – hammar

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

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

在第一种情况下发生的所有事情是GHCI猜测您输入的数字的类型。最精确的是Double,所以它只是假设另一个应该是Double并执行计算。但是,当您使用let表达式时,它只有一个数字作用,所以它决定1Integer2.0Double

编辑:GHCI不是真的“猜测”,它使用非常特定类型的默认规则在Haskell报告中定义。你可以阅读更多关于here的信息。

+3

首先,我upvoted。但是,因为我有挑剔的习惯:静态类型系统可以支持添加不同类型的数字。这就是Haskell定义的定义('(+):: Num a => a - > a - > a')不允许它。 (虽然类型推断的效果并不好,但这可能是它没有完成的原因。)另外,如果你明确地指出(“猜测”并不正确,恕我直言),你的答案可能会更清楚解释类型违约和文字是多态的事实。 – delnan

+3

“Haskell有一个静态类型系统,所以你永远无法将两种不同的类型加在一起。”这有点过分简化。很多语言都有静态类型系统,并允许将int添加到双打。这里的关键字是Haskell没有隐式转换。 – sepp2k

+0

是的,我不觉得我有足够好的处理类型违约,并解释它。随意添加您自己的答案与这些东西。 –

的首部作品,因为数字文字是多态(它们被解释为fromInteger literal RESP fromRational literal),所以在1 + 2.0,你真的有fromInteger 1 + fromRational 2,在没有其他约束,结果类型默认为Double

第二个是不是因为单态限制而工作。如果你绑定了一些没有类型签名和简单模式绑定的东西(name = expresion),那么该实体被赋予一个单形类型。对于文字1,我们有一个Num约束,因此,根据defaulting rules,其类型默认为Integer在绑定let a = 1。同样,小数文字的类型默认为Double

顺便说一下,如果你使用ghci中的:set -XNoMonomorphismRestriction,它将会起作用。

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

其他人已经很好地解决了这个问题的许多方面。我想谈谈为什么+的类型签名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是最简单的选择。多态性结果(如上述第三项建议)很酷,但有时可能过于通用而不便于使用。第三,哈斯克尔不是Blub。大多数(虽然可以说并非全部),关于Haskell的设计决策没有考虑流行语言的约定和期望。我经常喜欢说,学习Haskell的第一步是先忘记你认为你知道编程的一切。我敢肯定,大部分Haskeller都被Num类型类和其他各种Haskell好奇所绊住了,因为大多数人已经学会了更主流的语言。但要耐心点,你最终会到达Haskell涅磐。 :)