为什么我无法在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 一个事情在第一种情况下,但另一个在第二!
您可以使用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
的任何成员上工作,但两个参数必须是相同的类型。
这里的问题是GHCI和它建立类型的顺序,而不是Haskell本身。如果要将任意一个示例放入文件中(对let
表达式使用do
),它将编译并运行良好,因为GHC将使用整个函数作为上下文来确定文字1
和2.0
的文字类型。
在第一种情况下发生的所有事情是GHCI猜测您输入的数字的类型。最精确的是Double
,所以它只是假设另一个应该是Double
并执行计算。但是,当您使用let
表达式时,它只有一个数字作用,所以它决定1
是Integer
而2.0
是Double
。
编辑:GHCI不是真的“猜测”,它使用非常特定类型的默认规则在Haskell报告中定义。你可以阅读更多关于here的信息。
首先,我upvoted。但是,因为我有挑剔的习惯:静态类型系统可以支持添加不同类型的数字。这就是Haskell定义的定义('(+):: Num a => a - > a - > a')不允许它。 (虽然类型推断的效果并不好,但这可能是它没有完成的原因。)另外,如果你明确地指出(“猜测”并不正确,恕我直言),你的答案可能会更清楚解释类型违约和文字是多态的事实。 – delnan
“Haskell有一个静态类型系统,所以你永远无法将两种不同的类型加在一起。”这有点过分简化。很多语言都有静态类型系统,并允许将int添加到双打。这里的关键字是Haskell没有隐式转换。 – sepp2k
是的,我不觉得我有足够好的处理类型违约,并解释它。随意添加您自己的答案与这些东西。 –
的首部作品,因为数字文字是多态(它们被解释为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涅磐。 :)
由于meme去,“需要更多'fromIntegral'”。 –
有点让我想起'x.f()'vs'g = x.f; g();'在Javascript中。当变量破坏替代模型抽象时,它就会变得很糟糕。 – hugomg