一些解释是为了命令!
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
或者像格雷厄姆·赫顿(Graham Hutton)那样写道:
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
,也是决定哪些操作是最适合于特定的应用程序(伯德,1998年)的一些准则。
foldr的原型是folder ::(a-> b-> b)-> b-> [a]-> b
哈斯克尔程序员会说型的foldr
是(a -> b -> b) -> b -> [a] -> b
。
第一个参数是需要两个参数的函数,但是myFoldl的实现中的step函数使用了3个参数,我完全困惑
这是令人困惑和神奇的!我们玩弄一个小技巧,并用一个函数替换累加器,然后将其应用到初始值以产生结果。
格雷厄姆赫顿解释的伎俩转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
运营商有其递归论(克莱尼,1952年)的起源,而采用fold
作为编程语言日期的核心概念回APL的减少运营商(艾弗森,1962年),后来到FP的插入操作符(巴克斯(1978年)。在Haskell中,fold
列表的运算符可以定义如下:
fold :: (α → β → β) → β → ([α] → β)
fold f v [ ] = v
fold f v (x : xs) = f x (fold f v xs)
即,给定的函数f
类型的α → β → β
和值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
因此,folds的通用属性指出:
g = foldr k v
g
对于k
和,其中必须等于两个方程式v
:
g [] = v
g (x:xs) = k x (g xs)
从我们早期的折叠设计中,我们知道v == id
。但是对于第二个方程,我们需要计算的定义k
:
g (x:xs) = k x (g xs)
<=> g (x:xs) v = k x (g xs) v
<=> g xs (f v x) = k x (g xs) v
<= g' (f v x) = k x g' v
<=> k = \x g' -> (\a -> g' (f v x))
其中,代我们计算的定义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
被folder组合器代替,累加器成为通过f
列表中每个元素的链组成的功能相反的顺序构建的函数(因此,我们向左折叠而不是向右折叠)。
这肯定有点先进,因此要深入了解这种转换(即folds的通用属性),使转换成为可能,我建议使用Hutton的教程,链接如下。
参考文献
step = curry $ uncurry (&) <<< (flip f) *** (.)