我想制作一个折叠广泛输入的泛型函数(参见Making a single function work on lists, ByteStrings and Texts (and perhaps other similar representations))。作为one answer suggested,ListLike就是为了这个。它的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_
)的数据上定义它,它就不会回答原始问题。
答案 0 :(得分:7)
因此,目标是使用foldM
或foldr
重新实现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''
当然,这种等式推理不会告诉您有关您感兴趣的性能属性的任何信息。