福尔德。foldr函数组合-Haskell

福尔德。foldr函数组合-Haskell,haskell,fold,function-composition,Haskell,Fold,Function Composition,所以,我真的在绞尽脑汁试图理解foldl.foldr的组成。 以下是一个例子: (foldl.foldr) (+) 1 [[1,2,3],[4,5,6]] 结果是22,但这里到底发生了什么 在我看来,这就是正在发生的事情:foldl(+)1[6,15]。 我的疑问与foldr部分有关。它不应该将1添加到所有子列表中吗?像这样:foldr(+)1[1,2,3]。 在我脑子里,1只加了一次,对吗?(可能不会,但我想知道怎么做/为什么!) 我很困惑(也许是我制造了所有的困惑,哈哈)。 谢谢大家! (

所以,我真的在绞尽脑汁试图理解foldl.foldr的组成。 以下是一个例子:

(foldl.foldr) (+) 1 [[1,2,3],[4,5,6]]
结果是22,但这里到底发生了什么

在我看来,这就是正在发生的事情:
foldl(+)1[6,15]
。 我的疑问与
foldr
部分有关。它不应该将1添加到所有子列表中吗?像这样:
foldr(+)1[1,2,3]
。 在我脑子里,1只加了一次,对吗?(可能不会,但我想知道怎么做/为什么!)

我很困惑(也许是我制造了所有的困惑,哈哈)。 谢谢大家!

(foldl.foldr) (+) 1 [[1,2,3],[4,5,6]]
变成

foldl (foldr (+)) 1 [[1,2,3],[4,5,6]]
所以你得到了

foldl (foldr (+)) (foldr (+) 1 [1,2,3]) [[4,5,6]]
foldl
的第一步之后,或

foldl (foldr (+)) 7 [[4,5,6]]
如果我们评估应用的
foldr
(除非严格性分析器启动,否则实际上它将保持未评估的thunk,直到
foldl
遍历整个列表,但下一个表达式在评估后可读性更强),这将变为

foldl (foldr (+)) (foldr (+) 7 [4,5,6]) []
最后

foldl (foldr (+)) 22 []
~> 22

让我们检查一下
foldl。foldr
。他们的类型是

foldl :: (a -> b -> a) -> (a -> [b] -> a)
foldr :: (c -> d -> d) -> (d -> [c] -> d)
我特意使用了不同类型的变量,并添加了括号,这样我们就可以更清楚地看到,现在我们将它们视为一个参数的函数(它们的结果是函数)。查看
foldl
我们看到它是一种提升函数:给定一个函数,使用
b
a
生成
a
,我们提升它,使其在
[b]
上工作(通过重复计算)。函数
foldr
类似,只是参数颠倒了

现在,如果我们应用
foldl会发生什么。foldr
?首先,让我们推导类型:我们必须统一类型变量,以便
foldr
的结果与
foldl
的参数匹配。所以我们必须替换:
a=d,b=[c]

foldl :: (d -> [c] -> d) -> (d -> [[c]] -> d)
foldr :: (c -> d   -> d) -> (d -> [c] -> d)
所以我们得到了

foldl . foldr :: (c -> d -> d) -> (d -> [[c]] -> d)
它的意思是什么?首先,
foldr
将类型为
c->d->d
的参数提升到列表上,并反转其参数,以便得到
d->[c]->d
。接下来,
foldl
再次提起此函数以处理
[[c]]
-列表的
[c]

在您的例子中,被解除的操作是关联的,因此我们不关心其应用程序的顺序。双重提升只是创建一个函数,将该操作应用于所有嵌套元素

如果我们只使用foldl,效果会更好:我们可以举起多次,就像在

foldl . foldl . foldl . foldl
    :: (a -> b -> a) -> (a -> [[[[b]]]] -> a)
实际上,
(foldl.foldr)fzxs===foldr fz(concat$reverse-xs)

即使
f
是关联操作,正确的应用程序顺序也很重要,因为它会影响性能

我们从

(foldl.foldr) f z xs
foldl (foldr f) z xs
g=foldr f
[x1,x2,…,xn_1,xn]=xs写一会儿,这是

(...((z `g` x1) `g` x2) ... `g` xn)
(`g` xn) ((`g` xn_1) ... ((`g` x1) z) ... )
foldr f z $ concat [xn,xn_1, ..., x1]
foldr f z $ concat $ reverse xs
因此,在您的情况下,正确的还原顺序是

(foldl.foldr) 1 [[1,2,3],[4,5,6]]
4+(5+(6+(  1+(2+(3+  1)))))
22
也就是说

Prelude> (foldl.foldr) (:) [] [[1..3],[4..6],[7..8]]
[7,8,4,5,6,1,2,3]

类似地,
(foldl.foldl)f z xs==foldl f z$concat xs
。使用
snoc a b=a++[b]

Prelude> (foldl.foldl) snoc [] [[1..3],[4..6],[7..8]]
[1,2,3,4,5,6,7,8]
另外,
(foldl.foldl.foldl)f z xs==(foldl.foldl)(foldl f)z xs==foldl(foldl f)z$concat xs==(foldl.foldl)f z$concat xs==foldl f z$concat(concat xs)
,等等:

Prelude> (foldl.foldl.foldl) snoc [] [[[1..3],[4..6]],[[7..8]]]
[1,2,3,4,5,6,7,8]
Prelude> (foldl.foldr.foldl) snoc [] [[[1..3],[4..6]],[[7..8]]]
[7,8,1,2,3,4,5,6]
Prelude> (foldl.foldl.foldr) (:) [] [[[1..3],[4..6]],[[7..8]]]
[7,8,4,5,6,1,2,3]

我认为这不是正确的申请顺序,丹尼尔<代码>7
不会在您显示的时候就被强制执行,IMO。是的,除非进行优化,否则它将保持一个thunk,直到评估
foldl
产生的最终thunk为止。但过早地对其进行评估会减少键入的难度,使其更具可读性。即使对于关联操作,正确的应用程序顺序也很重要,因为它可能会对性能产生(潜在的巨大)影响。