将foldl写成文件夹混乱

时间:2019-01-30 23:24:52

标签: list haskell lambda higher-order-functions fold

我知道有关此的其他帖子,但我的稍有不同。

我有一个使用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有什么区别?

3 个答案:

答案 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呢?让我们分阶段进行:

  • 它需要3个参数,分别是xrb
  • 函数调用的结果为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
  • 因此,我们复杂的lambda函数的类型为a -> (b -> c) -> b -> c,其中c是我们尚未确定的某种类型。
  • 现在是关键点。此lambda作为foldr函数的第一个参数(fold函数)出现。因此,对于某些类型的d -> e -> ed,它必须具有类型e
  • 请记住,函数是咖喱的,尽管lambda的签名似乎带有3个参数,但我们可以通过将其重写为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的列表,并产生从bc 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来实现我们想要的向左折叠。

鉴于ab可以是任何东西,上述类型签名本身并不能为我们提供很多实现线索。但是,我们确实具有将它们关联的函数f-调用它需要一个b和一个a,并产生一个b。鉴于此(通过再次消除循环),上述功能要求我们在给定ab -> b函数和b的情况下产生另一个b,只能看到两种不平凡的方法:

  • 将该函数应用于b,然后将f应用于a和结果。
  • f应用于ab,然后将b -> b函数应用于结果

这两个中的第二个正是您要询问的lambda所做的事情,从现在看来,希望已经很明显了。 (第一个选项将写为\x r b -> f (r b) x。尽管我对此考虑不多,但我实际上不确定会产生什么总体效果。)

尽管感觉比实际要多,但我已经做了很多工作,因为我一直在努力。概括地说,给定foldlTest的列表和一个函数af :: 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上的答案,而是您在问题中引用的答案)似乎比必要的困难。我认为它旨在教您折叠的某些方面,但是显然这不能很好地工作。我尝试显示正在发生的事情,并在此过程中回答您的问题。

3)lambda函数的功能是什么?与常规文件夹有何不同?

整个过程只是在右折(foldl)的基础上建立了左折(foldr)并翻转了最后两个参数。等同于

foldTest f = flip (foldr (flip f))
foldTest f = flip (foldl f)

通过累加一个函数并通过lambda进行翻转,以一种相当模糊的方式进行。

1)10是否取代b-> b中的b? (那么b将是初始的累加器值)。我不理解的是(\ x r b-> r(f b x))部分。

是的,正确。 10代表左折的初始累积器。

2)每个变量的值是多少?我对这个lambda函数感到非常困惑。

要对发生的事情有一个直观的了解,我发现逐步进行实际的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 表达式中的初始累加器值,该表达式等于预期的左折,由右折构建。