关于Haskell中foldl函数类型定义的疑问及实现困惑
嘿,我来帮你把这两个关于Haskell foldl的疑问掰明白,都是初学者绕不开的点,咱们一步步来~
foldl类型签名里的t a是什么 先看foldl的完整类型签名:
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
这里的t可不是具体类型,而是一个类型构造器——你可以把它理解成“能装东西的容器模板”。比如:
- 列表的构造器是
[],所以t a在这里就是[] a(也就是我们平时写的[a]) Maybe也是Foldable的实例,那t a就可以是Maybe a- 要是你自己定义了一个二叉树类型
Tree,只要给它实现Foldable实例,t a就能是Tree a
那为什么不能写成Foldable a呢?这是Haskell里“种类(Kind)”不匹配的问题:
Foldable这个类型类是给类型构造器用的(比如[]、Maybe,它们的种类是* -> *,意思是“给我一个具体类型,我就能生成一个具体类型”)- 而
a是具体类型(比如Int、String,种类是*),根本没法放在Foldable的约束里——就像你不能把苹果放进只能装篮子的架子上一样,类型系统会直接报错。
foldl时,Foldable的“空情况”怎么处理 你说的没错,如果只针对列表写foldl,空情况就是[]直接返回初始值,但Foldable是通用的容器接口,那“空的Foldable”到底是什么?
其实答案分两种情况:
针对单个Foldable实例写foldl
每个Foldable实例都有自己的“空值”,你只要针对这个空值定义行为就行:- 列表的空值是
[]:foldl _ z [] = z - Maybe的空值是
Nothing:foldl _ z Nothing = z - 自定义二叉树的空值是
EmptyTree:foldl _ z EmptyTree = z
说白了,就是该类型里“不包含任何元素”的那个值,直接返回初始累加值z就对了。
- 列表的空值是
写通用的Foldable版foldl
如果你想写一个能适配所有Foldable实例的foldl,根本不用自己处理空情况——因为Foldable类型类已经帮你做了底层支撑。你可以基于Foldable的核心方法来实现,比如用foldr转写:myFoldl :: Foldable t => (b -> a -> b) -> b -> t a -> b myFoldl f z xs = foldr (\x acc -> acc `f` x) z (reverse xs)这里的
foldr已经处理了所有Foldable实例的空情况,你只需要关注怎么把foldr的右折叠逻辑转换成foldl的左折叠逻辑就行。当然,Haskell标准库的foldl实现更高效(用了惰性求值的技巧),但核心思路是一样的:借助Foldable的通用遍历能力,不用关心具体容器的空值是什么。
总结一下:t a是Foldable类型构造器t包裹具体类型a后的容器类型,而Foldable的“空情况”是每个实例自己定义的无元素值,通用实现时靠Foldable的核心方法来统一处理~
内容的提问来源于stack exchange,提问作者heapOverflow

