一些解释是有序的!
id 函数有什么用?有什么作用?为什么我们在这里需要它?
id
是恒等函数, , 并且在构建具有函数组合,id x = x
的函数链时用作零的等价物。您可以在 Prelude中找到它的定义。(.)
在上面的例子中,id 函数是 lambda 函数中的累加器吗?
累加器是通过重复的功能应用建立起来的功能。没有明确的 lambda,因为我们将累加器命名为step
. 如果需要,可以使用 lambda 编写它:
foldl f a bs = foldr (\b g x -> g (f x b)) id bs a
或者正如格雷厄姆赫顿所写的那样:
5.1foldl
运营商
现在让我们从suml
示例中概括并考虑标准运算符,该运算符通过使用函数组合值foldl
以从左到右的顺序处理列表的元素,并将值作为起始值:f
v
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 的原始定义更有效,因为它避免了(++)
对列表使用低效的 append 运算符。
上一节中函数计算的简单概括suml
显示了如何根据 重新定义foldl
函数fold
:
foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v
相反,不可能根据 重新定义fold
,foldl
因为
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)
也就是说,给定一个 type 的函数f
和一个 type的α → β → β
值,该函数
处理一个 type 列表,通过将列表末尾的nil 构造函数替换为value ,并将列表中的每个 cons 构造函数替换为功能。以这种方式,操作符封装了一个简单的递归模式来处理列表,其中列表的两个构造函数被其他值和函数简单地替换。列表上许多熟悉的函数都有一个简单的定义,使用.v
β
fold f v
[α]
β
[]
v
(:)
f
fold
fold
这看起来与我们的g
函数非常相似的递归方案。现在的诀窍是:使用手头所有可用的魔法(又名 Bird、Meertens 和 Malcolm),我们应用一个特殊规则,即fold 的通用属性,它是处理列表的函数的两个定义之间的等价g
,表示为:
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
列表中每个元素的组合链以相反的顺序构建(因此我们向左折叠而不是向右折叠)。
这肯定有点高级,所以要深入理解这种转换,folds 的通用属性,使转换成为可能,我推荐 Hutton 的教程,链接如下。
参考