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

Haskell 如何使用foldr/foldl定义foldM(如果可能的话)?,haskell,functional-programming,monads,fold,Haskell,Functional Programming,Monads,Fold,我想制作一个通用函数,可以折叠各种输入(请参阅)。正如我所说的那样,这就是原因。它的类为任何可折叠的东西定义了一个抽象。然而,我需要一个一元折叠。所以我需要用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

我想制作一个通用函数,可以折叠各种输入(请参阅)。正如我所说的那样,这就是原因。它的类为任何可折叠的东西定义了一个抽象。然而,我需要一个一元折叠。所以我需要用
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
,我通过将累加器包装到状态monad中,在
ListLike
上构建了
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
)的数据上定义它,那么它不能回答原来的问题。

因此目标是使用
foldr
foldl
重新实现
foldM
。应该是哪一个?我们希望延迟处理输入并允许infinte列表,这排除了
foldl
。那么它将是什么样子呢

下面是标准库中的
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
对此进行了抽象,但它仍然是一个指导原则)

那么应该用什么来取代
[]
?清楚地使用
返回一个
。但是
a
来自哪里?传递给
foldM
的不是初始
a
——如果列表不是空的,当
foldr
到达列表末尾时,累加器应该已经更改。因此,我们用一个函数替换
[]
,该函数接受一个累加器并在底层monad中返回它:
\a->returna
(或者干脆
return
)。这也给出了
foldr
将计算的对象类型:
a->ma

我们应该用什么来代替
?它需要是一个函数
b->(a->ma)->(a->ma)
,取列表的第一个元素、处理的尾部(当然是惰性的)和当前累加器。我们可以从上面的代码中得到提示来解决这个问题:它将是
\x resta->fax>>=rest
。因此,
foldM
的实现将是(调整类型变量以匹配上面代码中的类型变量):

事实上,现在您的程序可以使用任意大的输入,并在运行时输出结果

我们甚至可以归纳地证明,这些定义在语义上是相等的(尽管我们可能应该进行共归纳或归纳,以适应无限列表)

我们想展示

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''

当然,这个等式推理并没有告诉您任何您感兴趣的性能属性。

为什么不使用
Control.Monad
中的
foldM
?也可以使用严格版本的
foldl'
而不是
foldl
@Satvik,因为我需要它不仅为列表定义,而且为任何只暴露不同类型纯折叠(而不是一元折叠)的对象定义。糟糕的是,我在帖子中没有说清楚-我已经更正了类型签名。要以相反的顺序进行计算,请使用
foldr
而不是
foldl
@dave4420。我也尝试过,但没有用。我想要的正是
foldM
在列表中所做的。所以一个左折叠,不适用于无限列表。我将尝试用更有意义的内容替换
idx
foldM
对无限列表有效,因此一元操作是左折叠的,而累加器是右折叠的。或者类似的:-)@PetrPudlák:列表上的左折叠也可以写成右折叠:
  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''