Haskell 的类型推断非常聪明!我不能告诉你这实际上是如何推断的,但让我们来看看它可能是怎样的。现实可能不会太远。在这种情况下实际上不需要类型签名。
foldTree f = go where
go (Node x ts) = f x (map go ts)
foldTree 被定义为接受一个参数,go 被定义为接受一个参数,所以我们从一开始就知道这些是函数。
foldTree :: _a -> _b
foldTree f = go where
go :: _c -> _d
go (Node x ts) = f x (map go ts)
现在我们看到f 是用两个参数调用的,所以它实际上必须是(至少)两个参数的函数。
foldTree :: (_x -> _y -> _z) -> _b
foldTree f = go where
go :: _c -> _d
go (Node x ts) = f x (map go ts)
由于foldTree f = go和go :: _c -> _d,结果类型_b实际上必须是_c -> _d *:
foldTree :: (_x -> _y -> _z) -> _c -> _d
foldTree f = go where
go :: _c -> _d
go (Node x ts) = f x (map go ts)
传递给f(_y 类型)的第二个参数是map go ts。由于go :: _c -> _d,_y必须是[_d]
foldTree :: (_x -> [_d] -> _z) -> _c -> _d
foldTree f = go where
go :: _c -> _d
go (Node x ts) = f x (map go ts)
go 将其参数与Node x ts 匹配,而Node 是Tree 的数据构造函数,因此go 的参数(_c) 必须是Tree。
foldTree :: (_x -> [_d] -> _z) -> Tree _p -> _d
foldTree f = go where
go :: Tree _p -> _d
go (Node x ts) = f x (map go ts)
Node 构造函数的第一个字段作为f 的第一个参数传递,所以_x 和_p 必须相同:
foldTree :: (_x -> [_d] -> _z) -> Tree _x -> _d
foldTree f = go where
go :: Tree _x -> _d
go (Node x ts) = f x (map go ts)
由于go _被定义为f _ _,所以它们必须有相同类型的结果,所以_z就是_d:
foldTree :: (_x -> [_d] -> _d) -> Tree _x -> _d
foldTree f = go where
go :: Tree _x -> _d
go (Node x ts) = f x (map go ts)
哇。现在编译器检查以确保这些类型有效(它们确实有效),并将它们从“元变量”(意味着推理引擎不知道它们代表什么类型的变量)“概括”为量化类型变量(肯定是多态的),它得到
foldTree :: forall a b. (a -> [b] -> b) -> Tree a -> b
foldTree f = go where
go :: Tree a -> b
go (Node x ts) = f x (map go ts)
实际情况要复杂一些,但这应该会给你一个要点。
[*] 这一步有点作弊。我忽略了一个名为“let generalization”的功能,在这种情况下不需要它,实际上它被 GHC Haskell 中的几个语言扩展禁用。