一些解释是有序的!
id 函数有什么用?有什么作用?为什么我们在这里需要它?
id 是identity function、id x = x,在使用function composition、(.) 构建函数链时用作零的等价物。你可以找到它defined in the Prelude。
上面例子中,id函数就是lambda函数中的累加器?
累加器是一个通过重复的函数应用建立起来的函数。没有明确的 lambda,因为我们将累加器命名为 step。如果需要,您可以使用 lambda 编写它:
foldl f a bs = foldr (\b g x -> g (f x b)) id bs a
或者Graham Hutton would write:
5.1 foldl 运算符
现在让我们从suml 示例中进行概括,并考虑标准运算符foldl,它通过使用函数f 来组合值和值@,以从左到右的顺序处理列表的元素。 987654337@作为起始值:
foldl :: (β → α → β) → β → ([α] → β)
foldl f v [ ] = v
foldl f v (x : xs) = foldl f (f v x) xs
使用此运算符,suml 可以简单地通过 suml = foldl (+) 0 重新定义。许多其他函数可以使用foldl 以简单的方式定义。例如,标准函数reverse可以使用foldl重新定义如下:
reverse :: [α] → [α]
reverse = foldl (λxs x → x : xs) [ ]
这个定义比我们原来使用 fold 的定义更高效,因为它避免了对列表使用低效的追加操作符(++)。
上一节中函数suml的计算的简单概括说明了如何根据fold重新定义函数foldl:
foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v
相比之下,不可能根据foldl重新定义fold,因为
foldl 在其列表参数的尾部是严格的,但 fold 不是。关于fold 和foldl,有许多有用的“对偶定理”,还有一些指导方针可以确定哪个运算符最适合特定应用程序(Bird,1998 年)。
foldr 的原型是 foldr :: (a -> b -> b) -> b -> [a] -> b
Haskell 程序员会说foldr 的类型 是(a -> b -> b) -> b -> [a] -> b。
第一个参数是一个需要两个参数的函数,但是myFoldl的实现中的step函数使用了3个参数,我完全糊涂了
这是令人困惑和神奇的!我们玩了个把戏,用一个函数替换累加器,然后将其应用于初始值以产生结果。
Graham Hutton 在上述文章中解释了将foldl 转换为foldr 的技巧。我们首先写下foldl 的递归定义:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v [] = v
foldl f v (x : xs) = foldl f (f v x) xs
然后通过f上的静态参数转换重构它:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v xs = g xs v
where
g [] v = v
g (x:xs) v = g xs (f v x)
现在让我们重写g 以便将v 向内浮动:
foldl f v xs = g xs v
where
g [] = \v -> v
g (x:xs) = \v -> g xs (f v x)
这与将g 视为一个参数的函数相同,它返回一个函数:
foldl f v xs = g xs v
where
g [] = id
g (x:xs) = \v -> g xs (f v x)
现在我们有了g,一个递归遍历列表的函数,应用一些函数f。最终值是恒等函数,每一步也会产生一个函数。
但是,我们已经有一个非常相似的列表递归函数,foldr!
2 折叠运算符
fold 运算符起源于递归理论(Kleene,1952),而使用
fold 作为编程语言的中心概念可以追溯到 APL 的归约算子 (Iverson, 1962),然后是 FP 的插入算子 (Backus,
1978 年)。在 Haskell 中,列表的 fold 运算符可以定义如下:
fold :: (α → β → β) → β → ([α] → β)
fold f v [ ] = v
fold f v (x : xs) = f x (fold f v xs)
即,给定一个α → β → β 类型的函数f 和一个β 类型的值v,函数
fold f v 通过替换 nil 处理 [α] 类型的列表以给出 β 类型的值
构造函数[]在列表末尾的值v,列表中的每个cons构造函数(:)函数f。通过这种方式,fold 操作符封装了一个简单的递归模式来处理列表,其中列表的两个构造函数被其他值和函数简单地替换。列表中许多熟悉的函数都有一个使用fold 的简单定义。
这看起来与我们的g 函数非常相似。现在的诀窍:使用所有可用的魔法(又名 Bird、Meertens 和 Malcolm),我们应用一个特殊规则,fold 的通用属性,它是函数 @987654390 的两个定义之间的等价性@ 处理列表,表示为:
g [] = v
g (x:xs) = f x (g xs)
当且仅当
g = fold f v
因此,折叠的通用属性表明:
g = foldr k v
其中g 必须等价于两个等式,对于某些k 和v:
g [] = v
g (x:xs) = k x (g xs)
从我们早期的 foldl 设计中,我们知道v == id。但是对于第二个方程,我们需要
计算k的定义:
g (x:xs) = k x (g xs)
<=> g (x:xs) v = k x (g xs) v -- accumulator of functions
<=> g xs (f v x) = k x (g xs) v -- definition of foldl
<= g' (f v x) = k x g' v -- generalize (g xs) to g'
<=> k = \x g' -> (\a -> g' (f v x)) -- expand k. recursion captured in g'
其中,将我们计算出的 k 和 v 定义替换为
foldl 的定义为:
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v xs =
foldr
(\x g -> (\a -> g (f v x)))
id
xs
v
递归 g 被替换为 foldr 组合器,并且累加器成为一个函数,该函数通过列表的每个元素处的 f 组合链以相反的顺序构建(因此我们向左折叠而不是向右折叠) .
这肯定有点高级,所以要深入理解这种转换,折叠的通用属性,使转换成为可能,我推荐 Hutton 的教程,链接如下。
参考文献