在学习 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])

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

关于haskell - GHC 如何解释 `foldl + 0 [1,2,3]`(+ 周围没有括号)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45038549/

10-10 22:52