作为我正在尝试解决的问题的解决方案的一部分,我需要生成一个重复应用函数的列表,它的前一个结果。听起来非常像 iterate 函数,但有例外,该迭代具有
的签名iterate :: (a -> a) -> a -> [a]
我的函数存在于IO内部(我需要生成随机数),所以我需要更多的东西:
iterate'::(a -> IO a) -> a -> [a]
我查看了hoogle,但没有取得多大成功。
答案 0 :(得分:5)
如果使用pipes
库,实际上可以获得适用于无限列表的惰性迭代。定义非常简单:
import Pipes
iterate' :: (a -> IO a) -> a -> Producer a IO r
iterate' f a = do
yield a
a2 <- lift (f a)
iterate' f a2
例如,假设我们的步骤函数是:
step :: Int -> IO Int
step n = do
m <- readLn
return (n + m)
然后将iterate
应用于step
会生成Producer
,懒惰地提示用户输入并生成到目前为止读取的值的计数:
iterate' step 0 :: Producer Int IO ()
读取值的最简单方法是使用Producer
循环遍历for
:
main = runEffect $
for (iterate' step 0) $ \n -> do
lift (print n)
程序然后无休止地循环,请求用户输入并显示当前计数:
>>> main
0
10<Enter>
10
14<Enter>
24
5<Enter>
29
...
注意这是如何得到两个正确的,其他解决方案没有:
但是,我们可以像其他两个解决方案一样轻松过滤结果。例如,假设我想在计数大于100时停止。我可以写:
import qualified Pipes.Prelude as P
main = runEffect $
for (iterate' step 0 >-> P.takeWhile (< 100)) $ \n -> do
lift (print n)
你可以这样说:“当它们小于100时循环遍历迭代值。打印输出”。我们来试试吧:
>>> main
0
10<Enter>
10
20<Enter>
30
75<Enter>
>>> -- Done!
事实上,pipes
有另一个帮助函数用于打印输出值,因此您可以将上述内容简化为管道:
main = runEffect $ iterate' step 0 >-> P.takeWhile (< 100) >-> P.print
这清楚地显示了信息流。 iterate'
生成一个永不停止的流Int
,P.takeWhile
过滤器流,P.print
打印到达目的地的所有值。
如果您想了解有关pipes
库的更多信息,建议您阅读pipes
tutorial。
答案 1 :(得分:2)
您的功能存在于IO中,因此签名是:
iterate'::(a -> IO a) -> a -> IO [a]
问题是原始的iterate
函数返回一个无限列表,所以如果你试图在IO中做同样的事情,你将得到一个永远不会结束的动作。也许你应该添加一个条件来结束迭代。
iterate' action value = do
result <- action value
if condition result
then return []
else
rest <- iterate' action result
return $ result : rest
答案 2 :(得分:1)
首先,您的结果列表必须位于IO monad中,因此,迭代'必须生成IO [a]
,而不是'[a]
'
迭代可以定义为:
iterate (a -> a) -> a -> [a]
iterate f x = x : iterate f (f x)
所以我们可以很容易地创建一个iterateM
iterateM :: (a -> m a) -> m a -> [m a]
iterateM f x = x : iterateM f (x >>= f)
这仍然需要你的种子价值在monad中开始,并且还会给你一个monadic事物的列表,而不是一堆繁琐的东西。
所以,让我们稍微改变一下。
iterateM :: (a -> m a) -> a -> m [a]
iterateM f x = sequence $ go f (return x)
where
go f x = x : go f (x >>= f)
然而,这不起作用。这是因为序列首先运行每个动作,然后返回。 (你可以看一下如果你写一些safeDivide :: Double -> Double -> Maybe Double
,然后尝试类似fmap (take 10) $ iterateM (flip safeDivide 2) 1000
的东西。你会发现它没有终止。我不知道如何解决这个问题。