4

Let's take a function of type (Monad m) => a -> m a. For example:

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

I'd like to be able to apply it any number of times. The first thing I tried was

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

The problem is that it won't work for large n:

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

It doesn't work also the other way:

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

Actually, what works is using ($!) strictness operator

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

Is there a nicer or more idiomatic solution? Or probably a stricter one? I still easily get stack overflows if f is a heavy-weight function.

UPD: I found that writing times in a pointful form does not solve the problem of composing heavy-weight monadic actions neither. This works for f x = Just (x+1) but fails in the real world:

times f 0 a = return a
times f i a = (f $! a) >>= times f (i - 1)
sastanin
  • 40,473
  • 13
  • 103
  • 130

3 Answers3

4

If you make f strict as in

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

or

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

and leave the rest alone, your code runs in constant space (albeit slowly) even with very large n:

ghci> times 4000000000 f 3
Just 4000000003
Greg Bacon
  • 134,834
  • 32
  • 188
  • 245
  • Well, still the same problem if you run more iterations: ghci> iterateM_n 1000000 (Just . (+1)) 3 \n Just *** Exception: stack overflow \n ghci> iterateM_n' 1000000 (+) 0 (Just . (+1)) 3 \n Just *** Exception: stack overflow \n – sastanin Feb 10 '10 at 18:09
  • I like using `-XBangPatterns` instead of `seq` :-) Anyhow, if `f` is strict then there's no need for `>>=!` in my answer. Since it seems that OP's `f` isn't, this could help. – ephemient Feb 10 '10 at 20:10
  • Thanks! Accepted. This works with (Just . (+1)) indeed. I still have problems with my actual function, but at least now I see what can help. – sastanin Feb 12 '10 at 11:10
  • @jetxee Thank you! "How do I make this function strict?" seems like a great followup question. – Greg Bacon Feb 12 '10 at 15:21
2

I'd probably create some stricter variants of existing functions.

{-# 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

Perhaps your problems stem from the fact that seq only evaluates the first argument to WHNF? If you're working on a complex structure, you may need a deeper seq, like deepseq.

ephemient
  • 198,619
  • 38
  • 280
  • 391
  • This solution seems to work well, but not better than timesStrict, so it is not scalable neither. I have to look into deepseq. Thank you. – sastanin Feb 10 '10 at 20:00
1

I came up with this:

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

But it also overflows the stack on large numbers of n. I don't have the time right now to look into it more :-(

liwp
  • 6,746
  • 1
  • 27
  • 39