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] 被解析为 foldl0 [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

最通用的方法是让 sfoldl 完全相同的类型(结合它们的约束),给我们

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 尽职尽责地将其推断为有效类型。但是实际上没有人写过这些实例,所以在实际使用这个表达式时会遇到很多麻烦。实际上,您应该做的是修正括号。