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

时间:2012-10-14 09:38:51

标签: haskell functional-programming monads fold

我想制作一个折叠广泛输入的泛型函数(参见Making a single function work on lists, ByteStrings and Texts (and perhaps other similar representations))。作为one answer suggestedListLike就是为了这个。它的FoldableLL类为任何可折叠的东西定义了一个抽象。但是,我需要一个monadic折叠。因此,我需要根据foldM / foldl来定义foldr

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

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)

它会占用所有内存并失败。

我有一种感觉,问题是monadic计算是按错误的顺序组成的 - 比如((... >>= ...) >>= ...)而不是(... >>= (... >>= ...)),但到目前为止我还没有找到解决方法。< / p>


解决方法:由于ListLike公开了mapM_,我通过将累加器包装到状态monad中,在foldM上构建了ListLike

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_)的数据上定义它,它就不会回答原始问题。

1 个答案:

答案 0 :(得分:7)

因此,目标是使用foldMfoldr重新实现foldl。应该是哪一个?我们希望延迟处理输入并允许输入infinte列表,这将排除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来自哪里?它不会是传递给a的初始foldM - 如果列表不为空,当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''

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