让我在学习Haskell时遇到困难的一件事是foldl +
和foldl (+)
之间的区别。
Prelude> :t foldl + 0 [1,2,3]
:: (Num t1, Num ((b -> a -> b) -> b -> t a -> b),
Num ([t1] -> (b -> a -> b) -> b -> t a -> b), Foldable t) =>
(b -> a -> b) -> b -> t a -> b
VS
Prelude> :t foldl (+) 0 [1,2,3]
foldl (+) 0 [1,2,3] :: Num b => b
Haskell / GHC如何得出foldl + 0 [1,2,3]
的类型?我怎么能理解为什么它扩展到这种巨型?
答案 0 :(得分:8)
因为+
是一个中缀运算符,并且没有括号覆盖事物,
foldl + 0 [1,2,3]
解析为
(foldl) + (0 [1,2,3])
最简单的起点是foldl
的类型,这是众所周知的(如果您不知道,您可以通过:t foldl
询问GHCI。)
foldl :: Foldable f => (a -> b -> a) -> a -> f b -> a
接下来,另外一面补充。因为0
作为一个函数应用[1,2,3]
作为参数,所以必须有一个函数类型的Num实例,它将一些数字类型的列表作为输入,并产生输出...好吧,我们会做到的。
0 [1,2,3] :: (Num t, Num ([t] -> s)) => s
由于+
正在应用于这两个表达式,因此它们必须具有相同的类型。因此,我们必须统一
foldl :: Foldable f => (a -> b -> a) -> a -> f b -> a
与
0 [1,2,3] :: (Num t, Num ([t] -> s)) => s
最常见的方法是让s
与foldl
完全相同(结合他们的约束),给我们
0 [1,2,3] :: (Foldable f,
Num t,
Num ([t] -> (a -> b -> a) -> a -> f b -> a))
=> (a -> b -> a) -> a -> f b -> a
请记住,当然foldl
必须具有完全相同的类型:
foldl :: (Foldable f,
Num t,
Num ([t] -> (a -> b -> a) -> a -> f b -> a))
=> (a -> b -> a) -> a -> f b -> a
由于+
来自Num类型类,因此它们共享的类型也必须为Num。
foldl + 0 [1,2,3] :: (Foldable f,
Num t,
Num ([t] -> (a -> b -> a) -> a -> f b -> a),
Num ((a -> b -> a) -> a -> f b -> a))
=> (a -> b -> a) -> a -> f b -> a
正如你所看到的,模拟一些类型的重命名,就像GHC告诉你的那样。
但当然,这是一种相当愚蠢的类型。它是可能的有人会写出所有这些令人发指的Num实例,所以GHC尽职尽责地将其推断为有效类型。但实际上没有人写过这些实例,因此实际使用这个表达式会遇到很多麻烦。你应该做的就是修理你的括号。
答案 1 :(得分:0)
foldl + 0 [1,2,3]
被解析为foldl
和0 [1,2,3]
的总和,0
应用于列表[1,2,3]
。
答案 2 :(得分:0)
在Haskell中,括号不仅用于设置表达式求值的优先级。这两个陈述实际上是完全不同的。
符号+是运算符,所以它应该像a + b = c
一样工作。也就是说,左边是一个函数参数,右边是另一个函数参数。你说运算符是在 infix 表示法中使用的。
您可以通过括号将任何运算符转换为前缀表示法中的正常函数。 (+) a b = c
。
因此,你提供的两个表达方式完全不同。他们很自然地拥有不同类型的签名。