我刚开始学习Haskell,其中一个奇怪的事情就是具有多个参数的函数类型的语法。
考虑一个简单的例子:
(+) :: Num a => a -> a -> a
为什么我们需要这里的所有箭头?写Num Num Num -> Num
之类的东西会不会更有意义?
引擎盖下的原因是什么?我搜索了这个问题,但找不到任何真正有用的东西。
答案 0 :(得分:18)
令人困惑的第一件事是Num a =>
,所以我们现在完全忽略这一点。相反,我们考虑Int -> Int -> Int
,这是您提供的类型签名的一种可能的专业化。
Haskell中的函数几乎总是curried。这意味着多参数函数实际上是一个参数的函数,它返回一个接受下一个参数的函数,依此类推。
->
是关联的,因此Int -> Int -> Int
与Int -> (Int -> Int)
相同。
这也意味着这个定义
f :: Int -> Int -> Int
f x y = x + y
与
相同f :: Int -> Int -> Int
f x = \y -> x + y
实际上,Haskell中的所有函数都只使用一个参数。元组也存在,但它们是一等公民,因此它们不仅仅是一个参数列表。
Num a =>
是类型系统的一个不同方面。它表示类型变量a
必须是Num
类型类的实例。作为Num
实例的类型的常见示例包括Int
和Double
。所以Num
本身不是一个类型,它是一个类型类。 Num a =>
表示对类型变量a
的约束,它不是该函数的另一个参数。
(+)
方法是Num
类型类的成员,因此您必须以这种方式约束a
才能使用(+)
。如果您尝试为f
提供签名a -> a -> a
(没有约束),它将无效,因为a
完全不受约束,我们对它的类型一无所知。因此,我们无法在其上使用(+)
。
答案 1 :(得分:0)
函数类型签名中每个参数的类型可以包含空格,因此非常需要非空白分隔符,因此编译器(和人类!)可以区分它们。
例如,您可以使用参数化抽象数据类型:
data MyType a = MyValue a
和一个采用具体类型的函数(由MyType
类型构造函数构造):
myFunc :: MyType Int -> MyType Int -> String
如果您在参数之间没有->
,则签名看起来像
myFunc :: MyType Int MyType Int -> String -- Not valid code
并且编译器在计算函数的实际参数意图时会遇到更多麻烦(我想知道在某些情况下它甚至可能是不可能的吗?)。至少,它不太容易理解。