使用折叠编写monad动作

时间:2010-02-10 12:49:22

标签: haskell lazy-evaluation monads reduce

我们采用(Monad m) => a -> m a类型的函数。例如:

ghci> let f x = Just (x+1)

我希望能够多次应用它。我尝试的第一件事是

ghci> let times n f = foldr (>=>) return $ replicate n f

问题是它不适用于大型n

ghci> 3 `times` f $ 1
Just 4
ghci> 1000000 `times` f $ 1
Just *** Exception: stack overflow

它也不起作用:

ghci> let timesl n f = foldl' (<=<) return $ replicate n f
ghci> 3 `timesl` f $ 1
Just 4
ghci> 1000000 `timesl` f $ 1
Just *** Exception: stack overflow

实际上,有效的是使用($!)严格算子

ghci> let timesStrict n f = foldr1 ((>=>) . ($!)) $ replicate n f
ghci> 3 `timesStrict` f $ 1
Just 4
ghci> 10000000 `timesStrict` f $ 1
Just 10000001

是否有更好或更惯用的解决方案?或者可能更严格一个?如果f是一个重量级的函数,我仍然很容易得到堆栈溢出。

UPD:我发现以有点的形式写times并不能解决构成重量级monadic动作的问题。这适用于f x = Just(x + 1)但在现实世界中失败:

times f 0 a = return a
times f i a = (f $! a) >>= times f (i - 1)

3 个答案:

答案 0 :(得分:4)

如果你像{/ p>那样严格f

f x = let y = x+1 in y `seq` Just y

-- remember to enable -XBangPatterns
f !x = Just (x+1)

并且剩下的就是其余部分,即使是非常大的n,您的代码也会在恒定的空间中运行(尽管速度很慢):

ghci> times 4000000000 f 3
Just 4000000003

答案 1 :(得分:2)

我可能会创建一些现有功能的更严格的变体。

{-# LANGUAGE BangPatterns #-}
iterate' f !x = x : iterate' f (f x)
ma >>=! f = do !a <- ma; f a
times' n f a = iterate' (>>=! f) (return a) !! n

也许你的问题源于seq只评估WHNF的第一个参数这一事实?如果您正在处理复杂的结构,则可能需要更深入的seq,例如deepseq

答案 2 :(得分:1)

我想出了这个:

 last $ take n $ iterate (>>= f) $ Just 1

但它也会在大量n上溢出堆栈。我现在没有时间更多地研究它: - (