我试图永远重复一个 IO 操作,但是将一个执行的结果输入到下一个执行中。像这样的东西:
-- poorly named
iterateM :: Monad m => (a -> m a) -> a -> m b
iterateM f a = f a >>= iterateM f
Hoogle 似乎对我没有帮助,但我看到很多功能看起来非常接近我想要的功能,但似乎没有一个功能完全符合我的要求。
你是对的,我不知道这种特殊循环在哪里实现。你的实现看起来不错;为什么不将它作为补丁提交给monad-loops包呢?
好吧,我希望iterateM
组合器具有这种类型签名:
iterateM :: (Monad m) => (a -> m a) -> a -> m [a]
当然,这不是一个非常有用的组合器,因为您无法在大多数 monad 中提取结果。与组合器的基本命名标准一起使用的更明智的名称是iterateM_
:
iterateM_ :: (Monad m) => (a -> m a) -> a -> m b
iterateM_ f = fix $ \again x -> f x >>= again
这个组合器很有用:
countFrom :: (Enum a) => a -> IO b
countFrom = iterateM_ (\x -> succ x <$ print x)
但是,为了简单起见,我只会使用fix
或显式递归。显式递归代码不会更长或更不可读:
countFrom :: (Enum a) => a -> IO b
countFrom = fix (\again x -> print x >> again (succ x))
我相信您在标准库中看不到它的原因是它永远不会终止。iterate 函数可以利用惰性列表来允许您使用take
结果列表上的函数指定终止。在这里,你的结果是一元的,所以这是不可能的。
显然你的想法的精神是可以做到的。它只是看起来有点不同:
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
这实际上可以forever
用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