如何使用 foldr/foldl 定义 foldM(如果可能)?

Posted

技术标签:

【中文标题】如何使用 foldr/foldl 定义 foldM(如果可能)?【英文标题】:How to define foldM using foldr/foldl (if it is possible)? 【发布时间】:2012-10-14 09:38:51 【问题描述】:

我想制作一个通用函数,可以折叠各种输入(请参阅Making a single function work on lists, ByteStrings and Texts (and perhaps other similar representations))。作为one answer suggested,ListLike 就是为了这个。它的FoldableLL 类为任何可折叠的东西定义了一个抽象。但是,我需要一个单子折叠。所以我需要根据foldl/foldr来定义foldM

到目前为止,我的尝试都失败了。我试图定义

foldM'' :: (Monad m, LL.FoldableLL full a) => (b -> a -> m b) -> b -> full -> m b
foldM'' f z = LL.foldl (\acc x -> acc >>= (`f` x)) (return z)

但它在大量输入时会耗尽内存 - 它会构建一个大型未评估的计算树。例如,如果我将一个大文本文件传递给

main :: IO ()
main = getContents >>= foldM'' idx 0 >> return ()
  where
    -- print the current index if 'a' is found
    idx !i 'a' = print i >> return (i + 1)
    idx !i _   =            return (i + 1)

它会耗尽所有内存并失败。

我感觉问题在于一元计算的组合顺序错误——比如((... >>= ...) >>= ...) 而不是(... >>= (... >>= ...)),但到目前为止我还没有找到解决方法。


解决方法:由于ListLike 暴露了mapM_,我通过将累加器包装到状态单子中,在ListLikes 上构造了foldM

modifyT :: (Monad m) => (s -> m s) -> StateT s m ()
modifyT f = get >>= \x -> lift (f x) >>= put

foldLLM :: (LL.ListLike full a, Monad m) => (b -> a -> m b) -> b -> full -> m b
foldLLM f z c = execStateT (LL.mapM_ (\x -> modifyT (\b -> f b x)) c) z

虽然这适用于大型数据集,但它不是很好。如果可以在仅 FoldableLL(没有 mapM_)的数据上定义它,它并没有回答最初的问题。

【问题讨论】:

为什么不使用foldM from Control.Monad ?还要使用严格版本foldl' 而不是foldl @Satvik 因为我不仅需要它来定义列表,还需要定义任何FoldableLL,它只暴露不同种类的纯折叠,而不是单子折叠。我的错,我没有在帖子中说清楚 - 我已经更正了类型签名。 要以相反的顺序组合计算,请使用foldr 而不是foldl @dave4420 我也试过了,但是没用。 【参考方案1】:

所以目标是使用foldrfoldl 重新实现foldM。应该是哪一个?我们希望延迟处理输入并允许无限列表,这排除了foldl。所以foldr会是这样吗?

这是标准库中foldM 的定义。

foldM             :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM _ a []      =  return a
foldM f a (x:xs)  =  f a x >>= \fax -> foldM f fax xs

关于foldr 要记住的一点是,它的参数只是替换了列表中的[]:ListLike 对此进行了抽象,但它仍然作为指导原则)。

那么[] 应该替换成什么呢?显然是return a。但是a 是从哪里来的呢?传递给foldM 的不是最初的a——如果列表不为空,当foldr 到达列表末尾时,累加器应该已经改变。因此,我们将[] 替换为一个接受累加器并在底层monad 中返回它的函数:\a -> return a(或简称return)。这也给出了foldr 将计算的事物的类型:a -> m a

我们应该用什么替换:?它需要是一个函数b -> (a -> m a) -> (a -> m a),获取列表的第一个元素、处理后的尾部(当然是懒惰的)和当前的累加器。我们可以从上面的代码中得到提示:它将是\x rest a -> f a x >>= rest。所以我们对foldM 的实现将是(在上面的代码中调整类型变量以匹配它们):

foldM'' :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM'' f z list = foldr (\x rest a -> f a x >>= rest) return list z

事实上,现在您的程序可以消耗任意大的输入,并在执行过程中吐出结果。

我们甚至可以通过归纳证明定义在语义上是相等的(尽管我们可能应该进行共归纳或取归纳来满足无限列表的需要)。

我们想展示

foldM f a xs = foldM'' f a xs

所有xs :: [b]。对于xs = [],我们有

  foldM f a []
≡ return a     -- definition of foldM
≡ foldr (\x rest a -> f a x >>= rest) return [] a -- definition of foldr
≡ foldM'' f a [] -- definition of foldM''

并且,假设我们为xs 提供了它,我们为x:xs 显示它:

  foldM f a (x:xs)
≡ f a x >>= \fax -> foldM f fax xs --definition of foldM
≡ f a x >>= \fax -> foldM'' f fax xs -- induction hypothesis
≡ f a x >>= \fax -> foldr (\x rest a -> f a x >>= rest) return xs fax -- definition of foldM''
≡ f a x >>= foldr (\x rest a -> f a x >>= rest) return xs -- eta expansion
≡ foldr (\x rest a -> f a x >>= rest) return (x:xs) -- definition of foldr
≡ foldM'' f a (x:xs) -- definition of foldM''

当然,这种等式推理不会告诉您有关您感兴趣的性能属性的任何信息。

【讨论】:

我想要foldM 在列表中所做的一切。所以左折叠,无限列表不行。我会尝试用更有意义的东西替换idx foldM 确实适用于无限列表,因此单子动作是左折叠的,而累加器是右折叠的。或者类似的东西:-) @PetrPudlák:列表的左折叠也可以写成右折叠:haskell.org/haskellwiki/Foldl_as_foldr

以上是关于如何使用 foldr/foldl 定义 foldM(如果可能)?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建自定义 JQuery 函数以及如何使用它?

如何在Java类中定义接口属性并如何使用

如何使用 Comparator 定义自定义排序顺序?

如何使用自定义表单和自定义流程

如何使用自定义错误消息捕获“TypeError:无法读取未定义的属性(读取'0')”?

如何使用自定义内容定义 UserControl?