如何在IO上下文中生成函数的重复应用程序列表到它的先前结果

时间:2013-11-27 08:46:35

标签: haskell

作为我正在尝试解决的问题的解决方案的一部分,我需要生成一个重复应用函数的列表,它的前一个结果。听起来非常像 iterate 函数,但有例外,该迭代具有

的签名
iterate :: (a -> a) -> a -> [a]

我的函数存在于IO内部(我需要生成随机数),所以我需要更多的东西:

iterate'::(a -> IO a) -> a -> [a]

我查看了hoogle,但没有取得多大成功。

3 个答案:

答案 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'生成一个永不停止的流IntP.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的东西。你会发现它没有终止。我不知道如何解决这个问题。