我知道有关此的其他帖子,但我的稍有不同。
我有一个使用foldl
执行foldr
任务的功能。我已经给出了解决方案,但是希望对您有所帮助。
foldlTest:: (b -> a -> b) -> [a] -> (b -> b)
foldlTest f xs = foldr (\x r b -> r (f b x))
(\b -> b)
xs
然后使用类似这样的名称进行调用:
foldlTest (-) [1,2,3] 10 = -4
我了解的第一件事是我的函数接受2个参数,但是在上述测试用例中给出了3个。这意味着10
将参与我假设的lambda表达式。
1)10
是否代替b
中的b -> b
? (那么b将是初始累加器值)
我不了解的是(\x r b -> r (f b x))
部分的作用。
2)每个变量的值是多少?我对这个lambda函数感到非常困惑。
3)lambda函数到底有什么作用?它与常规foldr
有什么区别?
答案 0 :(得分:5)
好的,因为我们的居民Haskell专家都还没有加紧解释这一点,所以我认为我可以去了。请大家,随时纠正您发现的错误,因为我真的只是想在这里找到答案,而从本质上讲,以下内容会有些麻烦。
首先,像在Haskell中一样,最好查看类型:
Prelude> :t foldl
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
由于我们仅对此处的列表感兴趣,而不对通用的Foldable
感兴趣,因此我们将其专门用于:
foldl :: (b -> a -> b) -> b -> [a] -> b
并与您获得的功能进行比较:
foldlTest:: (b -> a -> b) -> [a] -> (b -> b)
由于Haskell函数是咖喱函数,这是另一种说法,即类型签名中的->
箭头是右关联的,因此最后一对括号是不必要的,因此与以下内容相同:
foldlTest:: (b -> a -> b) -> [a] -> b -> b
与上面的foldl
相比,我们看到它们是相同的,除了最后两个参数-[a]
和b
-已被翻转。
因此我们已经观察到,虽然库函数foldl
具有折叠功能,起始累加器和要折叠的列表以产生新的累加器,但foldlTest
版本却具有折叠函数,要折叠的列表和起始累加器,以产生新的累加器。听起来确实是一样的东西,但是如果我们现在重新引入我在几步前摘下的那对括号,我们会发现可以认为foldlTest
是您所展示的形式的:
获取一个折叠函数和一个列表,并生成一个函数b -> b
,该函数描述在列表上的折叠如何将初始累加器转换为最终结果。
尤其要注意,在此公式中,它返回的确实是一个函数。
因此,现在我们准备看一下您所看到的实际实现:
foldlTest f xs = foldr (\x r b -> r (f b x))
(\b -> b)
xs
我将第一个承认这相当复杂,甚至令人困惑。与以往一样,让我们检查类型。
输入类型很简单。从上面的讨论中我们知道f
的类型为b -> a -> b
,而xs
的类型为[a]
。
好的,那lambda呢?让我们分阶段进行:
x
,r
和b
。r (f b x)
。如果我们坐下来考虑一下,这已经告诉了我们很多!f
的类型为b -> a -> b
,因此从f b x
中我们知道b
的类型为b
,而x
的类型输入a
。r
,我们看到它必须是一个函数,因为它已应用于f b x
。并且我们知道后者的类型为b
(来自f
的类型签名)。因此,r
的类型为b -> c
,某些类型为c
。a -> (b -> c) -> b -> c
,其中c
是我们尚未确定的某种类型。foldr
函数的第一个参数(fold函数)出现。因此,对于某些类型的d -> e -> e
和d
,它必须具有类型e
。a -> (b -> c) -> (b -> c)
来将其减少为2个。这与我们知道foldr
正在寻找的签名完全匹配,其中d
等于a
,而e
等于b -> c
。我们专门研究了foldr
的签名,以便它接受这种类型的功能,我们发现它是:
foldr :: (a -> (b -> c) -> (b -> c)) -> (b -> c) -> [a] -> (b -> c)`
我们仍然不知道c
是什么-但我们不必再想太多了。上面是折叠的签名,该折叠遍历a
的列表,并产生从b
到c
的 function 。 foldr
的下一个参数是b -> c
,类型为\b -> b
(通过我们尝试解密的实现)。当然,这只是身份功能,而至关重要的是,它是从类型到自身的功能。因此b -> c
类型实际上必须为b -> b
,换句话说c
始终与b
相同!
因此lambda必须具有以下类型:
a -> (b -> b) -> (b -> b)
它需要一个a
和一个b
的内态(仅表示从b
到其自身的函数),并返回另一个b
的内态。这就是我们要折叠的a
列表的功能,以恒等函数为起点,以产生内同性b -> b
来实现我们想要的向左折叠。>
鉴于a
和b
可以是任何东西,上述类型签名本身并不能为我们提供很多实现线索。但是,我们确实具有将它们关联的函数f
-调用它需要一个b
和一个a
,并产生一个b
。鉴于此(通过再次消除循环),上述功能要求我们在给定a
,b -> b
函数和b
的情况下产生另一个b
,只能看到两种不平凡的方法:
b
,然后将f
应用于a
和结果。f
应用于a
和b
,然后将b -> b
函数应用于结果这两个中的第二个正是您要询问的lambda所做的事情,从现在看来,希望已经很明显了。 (第一个选项将写为\x r b -> f (r b) x
。尽管我对此考虑不多,但我实际上不确定会产生什么总体效果。)
尽管感觉比实际要多,但我已经做了很多工作,因为我一直在努力。概括地说,给定foldlTest
的列表和一个函数a
,f :: b -> a -> b
函数的作用是产生一个以身份函数开头的函数b -> b
,然后从列表向右走,将当前函数r :: b -> b
更改为将b
发送到r (f b x)
的函数-其中x :: a
是列表的元素我们目前在。
这是foldlTest
所做的相当算法的描述。让我们尝试看看它对实际列表的作用-不是具体列表,而是假设3元素列表[a1, a2, a3]
。我们从身份函数\b -> b
开始,然后将其转换为:
b -> f b a3
(回想起r
是身份功能)b -> f (f b a2) a3
(这只是将先前的功能r
替换为\b -> r (f b x)
,而a2
现在扮演x
的角色)b -> f (f (f b a1) a2) a3
我希望您现在可以看到,这看起来非常像使用相同功能f
从左侧折叠列表。而且,“看起来很像”实际上是相同的! (如果您以前从未看过或尝试过,请尝试写出foldl f b [a1, a2, a3]
的连续阶段,您会看到相同的模式。)
很抱歉,这有点麻烦,但是我希望这能给您足够的信息来回答您提出的问题。而且不用担心它是否会使您的大脑受到一点伤害-它也属于我的! :)
答案 1 :(得分:0)
您得到的答案(不是SO上的答案,而是您在问题中引用的答案)似乎比必要的困难。我认为它旨在教您折叠的某些方面,但是显然这不能很好地工作。我尝试显示正在发生的事情,并在此过程中回答您的问题。
整个过程只是在右折(foldl
)的基础上建立了左折(foldr
)并翻转了最后两个参数。等同于
foldTest f = flip (foldr (flip f))
foldTest f = flip (foldl f)
通过累加一个函数并通过lambda进行翻转,以一种相当模糊的方式进行。
是的,正确。 10代表左折的初始累积器。
要对发生的事情有一个直观的了解,我发现逐步进行实际的lambda演算会有所帮助:
foldTest (-) [1,2,3] 10
foldTest (-) (1:(2:(3:[]))) 10
-- remember the definition of foldTest which is given in a point-free way
foldlTest f xs = foldr (\x r b -> r (f b x)) (\b -> b) xs
-- add the hidden parameter and you get
foldlTest f xs b' = (foldr (\x r b -> r (f b x)) (\b -> b) xs) b'
-- apply that definition with f = (-), xs = (1:(2:(3:[]))), b' = 10
(foldr (\x r b -> r ((-) b x)) (\b -> b) (1:(2:(3:[])))) 10
(foldr (\x r b -> r (b - x)) (\b -> b) (1:(2:(3:[])))) 10
-- the inner lambda function is curried, thus we can write it as
-- \x r (\b -> r (b - x)), which is equivalent but will be handy later on.
(
foldr (\x r -> (\b -> r (b - x))) (\b -> b) (1:(2:(3:[])))
) 10
-- keep in mind foldr is defined as
foldr f' b'' [] = b''
foldr f' b'' (x:xs') = f' x (foldr f' b'' xs')
-- apply second row of foldr with f' = (\x r -> (\b -> r (b - x))),
-- b'' = (\b -> b), x = 1 and xs' = (2:(3:[]))
(
(\x r -> (\b -> r (b - x))) 1 (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[])))
) 10
-- apply accumulation lambda for the first time with x = 1,
-- r = foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[])) gives
(
\b -> (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[]))) (b - 1)
) 10
-- now we repeat the process for the inner folds
(
\b -> (
foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[]))
) (b - 1)
) 10
(
\b -> (
(\x r -> (\b -> r (b - x))) 2 (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (3:[]))
) (b - 1)
) 10
(
\b -> (
\b -> (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (3:[])) (b - 2)
) (b - 1)
) 10
(
\b -> (
\b -> (
foldr (\x r -> (\b -> r (b - x))) (\b -> b) (3:[])
) (b - 2)
) (b - 1)
) 10
(
\b -> (
\b -> (
(\x r -> (\b -> r (b - x))) 3 (foldr (\x r -> (\b -> r (b - x))) (\b -> b) [])
) (b - 2)
) (b - 1)
) 10
(
\b -> (
\b -> (
\b -> (foldr (\x r -> (\b -> r (b - x))) (\b -> b) [])) (b - 3)
) (b - 2)
) (b - 1)
) 10
(
\b -> (
\b -> (
\b -> (
foldr (\x r -> (\b -> r (b - x))) (\b -> b) []
) (b - 3)
) (b - 2)
) (b - 1)
) 10
-- Now the first line of foldr's definition comes in to play
(
\b -> (
\b -> (
\b -> (
\b -> b
) (b - 3)
) (b - 2)
) (b - 1)
) 10
-- applying those lambdas gives us
(
\b -> (
\b -> (
\b -> (
\b -> b
) (b - 3)
) (b - 2)
) (b - 1)
) 10
-- So we can see that the foldTest function built another function
-- doing what we want:
(\b -> (\b -> (\b -> (\b -> b) (b - 3)) (b - 2)) (b - 1)) 10
(\b -> (\b -> (\b -> b) (b - 3)) (b - 2)) (10 - 1)
(\b -> (\b -> b) (b - 3)) ((10 - 1) - 2)
(\b -> b) (((10 - 1) - 2) - 3)
(((10 - 1) - 2) - 3)
((9 - 2) - 3)
(7 - 3)
4
答案 2 :(得分:0)
根据 foldlTest
的定义,我们拥有
foldlTest (-) xs b = foldr g n xs b
where
n b = b
g x r b = r (b - x)
根据 foldr
的定义,我们拥有
foldr g n [x,y,z] = g x (foldr g n [y,z])
而且
foldr g n [x,y,z] b = g x (foldr g n [y,z]) b -- (1)
---- r -----------
= foldr g n [y,z] (b-x)
(在“ foldlTest
内部使用”时),因此,通过重复应用(1)
,
= g y (foldr g n [z]) (b-x)
= foldr g n [z] ((b-x)-y)
= g z (foldr g n [] ) ((b-x)-y)
= foldr g n [] (((b-x)-y)-z)
= n (((b-x)-y)-z)
= (((b-x)-y)-z)
因此,由于g
是尾递归的,因此由右折直接向上构建了一个等效于左折的表达式。因此
foldlTest (-) [1,2,3] 10
-- [x,y,z] b
==
(((10 - 1) - 2) - 3))
==
foldl (-) 10 [1,2,3]
,因此我们看到 否 ,b
中的n = (\b -> b)
确实 不是 < / strong>接受10
,但接受 整个表达式 ,等同于 左折 由正确折叠构建。
但是是的,10
是表达式中的初始累加器值,该表达式等于预期的左折,由右折构建。