GHC 如何解释 `foldl + 0 [1,2,3]`(+ 周围没有括号)?
How does GHC interpret `foldl + 0 [1,2,3]` (without parentheses around +)?
在学习 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
对
Prelude> :t foldl (+) 0 [1,2,3]
foldl (+) 0 [1,2,3] :: Num b => b
Haskell/GHC如何导出foldl + 0 [1,2,3]
的类型?我如何理解为什么它扩展到这种巨型?
foldl + 0 [1,2,3]
被解析为 foldl
和 0 [1,2,3]
之和,即 0
应用于列表 [1,2,3]
.
在Haskell中,括号不仅用于设置表达式求值的优先级。两者的说法其实大相径庭
符号 + 是一个 运算符 ,因此它应该像 a + b = c
一样工作。也就是说,一个函数参数在左边,另一个在右边。你说运算符是在 infix 表示法中使用的。
您可以通过括号将任何运算符转换为 prefix 表示法中的普通函数。 (+) a b = c
。
因此,您提供的两个表达式有很大不同。他们有不同的类型签名是很自然的。
因为 +
是一个中缀运算符并且没有括号覆盖,
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 尽职尽责地将其推断为有效类型。但是实际上没有人写过这些实例,所以在实际使用这个表达式时会遇到很多麻烦。实际上,您应该做的是修正括号。
在学习 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
对
Prelude> :t foldl (+) 0 [1,2,3]
foldl (+) 0 [1,2,3] :: Num b => b
Haskell/GHC如何导出foldl + 0 [1,2,3]
的类型?我如何理解为什么它扩展到这种巨型?
foldl + 0 [1,2,3]
被解析为 foldl
和 0 [1,2,3]
之和,即 0
应用于列表 [1,2,3]
.
在Haskell中,括号不仅用于设置表达式求值的优先级。两者的说法其实大相径庭
符号 + 是一个 运算符 ,因此它应该像 a + b = c
一样工作。也就是说,一个函数参数在左边,另一个在右边。你说运算符是在 infix 表示法中使用的。
您可以通过括号将任何运算符转换为 prefix 表示法中的普通函数。 (+) a b = c
。
因此,您提供的两个表达式有很大不同。他们有不同的类型签名是很自然的。
因为 +
是一个中缀运算符并且没有括号覆盖,
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 尽职尽责地将其推断为有效类型。但是实际上没有人写过这些实例,所以在实际使用这个表达式时会遇到很多麻烦。实际上,您应该做的是修正括号。