对Haskell来说非常陌生,试图理解类型类和变量如何交互。
我首先要玩的是:
i :: a; i = 1
我的期望是,由于我被尽可能通用地键入,我应该能够绝对地为它分配任何东西。 (我知道我可能无法对变量 i 做任何事情,但这并不重要。
但是,我错了。 上面给出了一个错误,并要求它是:
i :: Num a => a; i = 1
在玩了一会儿之后,我想出了以下内容:
g :: Num a => a -> a; g a = a + 1
g 1
(returned 2)
gg :: Num a => a; gg = g 1
gg
(returned 2)
好的......到目前为止一切顺利。 让我们尝试一个小数参数。
g :: Num a => a -> a; g a = a + 1
g 1.3
(returned 2.3)
gg :: Num a => a; gg = g 1.3
(error)
所以,请...导致这种情况的变量是什么? 从非函数式编程背景来看,它"看起来"就像我有一个函数,该函数返回一个实现 Num 类型的值,并尝试将其分配给具有实现 Num 类型的变量。 然而,分配失败了。
我敢肯定这是我的一些基本误解。 这可能是阻止第一个示例工作的原因。我真的很想在我开始犯更严重的概念错误之前把它理顺。
i :: a; i = 1
我的期望是,由于我被尽可能通用地键入,我应该能够绝对地为它分配任何东西。(我知道我可能无法对变量 i 做任何事情,但这并不重要。
不,这是相反的方式。该类型表示以后如何使用该值,即它声明用户可以使用i
假装它是当时可能需要的任何类型。本质上,用户选择a
实际的类型,并且定义i :: a
的代码必须符合用户的任何此类选择。
(顺便说一下,我们通常称i = 1
为"绑定"或"定义",而不是"赋值",因为这意味着我们以后可以重新赋值。
gg :: Num a => a; gg = g 1.3 (error)
同样的原则也适用于这里。gg
声称是用户可能想要的任何数字类型,但如果用户稍后选择,例如,Int
定义g 1.3
不适合Int
。
用户可以使用显式签名(print (gg :: Int)
)来选择类型,或者将其置于"强制"类型的上下文中(print (length "hello" + gg)
强制Int
,因为length
返回Int
)。
如果您熟悉其他一些语言中的"泛型",则可以使用以下代码进行比较:
-- Haskell
i :: a
i = 1 -- type error
-- pseudo-Java
<A> A getI() {
return 1; -- type error
}
从更理论的角度来看,您正在考虑错误的量词。当你写i :: a
时,你正在考虑i :: exists a . a
(不是真正的Haskell类型),它读作"i
是某种类型的值(在定义时选择)"。相反,在Haskell中,i :: a
的意思是i :: forall a . a
,读作"i
是所有类型的值(使用时可能需要的任何类型)"。因此,它归结为"存在"与"forall",或者"谁选择a
实际是什么类型"。