iterate + forever = iterateM? Repeating an action

2019-06-17 08:21发布

问题:

I'm trying to repeat an IO action forever, but feeding the result of one execution into the next. Something like this:

-- poorly named
iterateM :: Monad m => (a -> m a) -> a -> m b
iterateM f a = f a >>= iterateM f

Hoogle didn't seem to help me, but I see plenty of functions which look enticingly close to what I want, but none seem to come together to be exactly it.

回答1:

You're right, I don't know of a place this particular kind of loop is implemented. Your implementation looks fine; why not submit it as a patch to the monad-loops package?



回答2:

Well, I would expect an iterateM combinator to have this type signature:

iterateM :: (Monad m) => (a -> m a) -> a -> m [a]

Of course this is not a very useful combinator, because you couldn't extract the result in most monads. A more sensible name to go with the base naming standard for your combinator would be iterateM_:

iterateM_ :: (Monad m) => (a -> m a) -> a -> m b
iterateM_ f = fix $ \again x -> f x >>= again

This combinator can be useful:

countFrom :: (Enum a) => a -> IO b
countFrom = iterateM_ (\x -> succ x <$ print x)

However, for the sake of simplicity I would just go with fix or explicit recursion. The explicitly recursive code isn't much longer or much less readable:

countFrom :: (Enum a) => a -> IO b
countFrom = fix (\again x -> print x >> again (succ x))


回答3:

I believe the reason you don't see this in the standard libraries is because it will never terminate. The iterate function can leverage lazy lists to allow you to specify termination using the take function on the result list. Here, your result is monadic, so this isn't possible.

Obviously the spirit of your idea can be done. It just has to look a little different:

iterateM :: Monad m => Int -> (a -> m a) -> a -> m a
iterateM 0 _ a = return a
iterateM n f a = f a >>= iterateM (n-1) f


回答4:

This can actually be written in terms of forever using StateT.

import Control.Monad.Trans.State
import Control.Monad.Trans.Class (lift)
import Control.Monad (forever)

iterateM :: Monad m => (a -> m a) -> a -> m b
iterateM f = evalStateT $ forever $ get >>= lift . f >>= put