【问题标题】:foldl . foldr function composition - Haskell折叠foldr 函数组合 - Haskell
【发布时间】:2013-01-11 04:45:16
【问题描述】:

所以,我真的在努力理解 foldl.foldr 的组成。 这是一个例子:

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

结果是 22,但这里到底发生了什么?

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

我很困惑(也许让所有的困惑,哈哈)。 谢谢!

【问题讨论】:

    标签: haskell fold function-composition


    【解决方案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
    

    【讨论】:

    • 我认为这不是正确的应用程序顺序,Daniel。 7 不会像你展示的那样早被强迫,IMO。
    • 是的,除非进行优化,否则它将保持一个 thunk,直到评估 foldl 产生的最终结果 thunk。但是过早地评估它会减少打字并且使其更具可读性。
    【解决方案2】:

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

    foldl :: (a -> b -> a) -> (a -> [b] -> a)
    foldr :: (c -> d -> d) -> (d -> [c] -> d)
    

    我有意使用了不同的类型变量并添加了括号,以便我们现在将它们视为一个参数的函数(并且它们的结果是函数)变得更加明显。查看foldl,我们看到它是一种提升函数:给定一个使用ba 产生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)
    

    【讨论】:

    • 即使对于关联操作,正确的应用程序顺序也很重要,因为它可能对性能产生(可能很大)影响。
    【解决方案3】:

    其实(foldl.foldr) f z xs === foldr f z (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]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-30
      • 2020-03-03
      • 1970-01-01
      相关资源
      最近更新 更多