【问题标题】:How to implement this kind of IO-based loop using higher order functions?如何使用高阶函数来实现这种基于 IO 的循环?
【发布时间】:2016-06-16 00:07:15
【问题描述】:

我有一些类似下面的代码,它根据从磁盘读取的随机 samples 更新 state

myloop 0 state = return state
myloop n state = do
  sample <- getRandomSampleFromFile
  myloop (n - 1) (process state sample)

如何以惯用的方式使用高阶函数来避免显式函数和递归(最好不要引入庞大的库)?


澄清:我不能只做replicateM n getRandomSampleFromFile,因为我需要处理大量样本,并且首先将所有样本读入内存是不可行的。

【问题讨论】:

  • 我不确定 replicateM 是否必须读取内存中的所有样本。我可能错了。
  • @mb14 当然会!! replicateMsequencetraversemapM 仅在您将列表视为数组时才有意义。您也可以使用来自例如的真实数组类型。 vector 一看到这些词,就想“也许是一个流媒体库?”。在长列表中,它们将总是 累积列表并导致空间泄漏。这就是我们拥有流媒体库的原因。 (例如,可以将 replicateM & company 专门用于 IO,然后使用惰性 io 之类的东西,请参阅 twanvl.nl/blog/haskell/unsafe-sequence

标签: haskell higher-order-functions


【解决方案1】:

这应该提供一个提示:

> import Control.Monad
> foldM (\n x -> print (n,x) >> return (n+x)) 0 [10,20,30]
(0,10)
(10,20)
(30,30)
60

在您的情况下,n 是一个索引状态对(或者只有状态,如果计算中不需要索引),x 是手头的样本。

【讨论】:

    【解决方案2】:

    来自

    myloop 0 state = return state
    myloop n state = do
      sample <- getRandomSampleFromFile
      myloop (n - 1) (process state sample)
    

    拆分读取数据/处理数据

    xs <- mapM (const getRandomSampleFromFile) [1..n]
    

    现在取n 样品,简单折叠

    foldl process state xs
    

    你可以使用应用语法

    myloop n state = foldl process state <$> mapM (const getRandomSampleFromFile) [1..n]
    

    或(thk2 @andrás-kovács)

    myloop n state = foldl process state <$> replicateM m getRandomSampleFromFile
    

    如果你想中断读取过程(或在读取时处理数据),那么你必须进入 monad

    myloop n state = foldM acc state [1..n]
        where acc s _ | breakProcess s = return s
                      | otherwise      = process s <$> getRandomSampleFromFile
    

    但是折叠不会停止,你最初的方法(毕竟)看起来更好。

    myloop n state | breakProcess state = return state
                   | otherwise          = do
                                            x <- getRandomSampleFromFile
                                            myloop (n - 1) (process state x)
    

    无论如何,如果您正在寻找一些流处理,我鼓励使用conduitpipes...。

    (顺便提一下,你的getRandomSampleFromFile函数可能有硬编码配置,不好)

    【讨论】:

    • 我没有在原始问题中指出这一点,但这是否意味着我必须先从磁盘读取 all 随机样本?这对我的用例来说是不可行的。
    • mapM (const x) [1..n] = replicateM n x
    【解决方案3】:

    一旦您了解了它的工作原理,foldr 在 monad 中工作时会出奇地多才多艺:

    myloop n = foldr w return [1..n] where
        w _ k state = do
            sample <- getRandomSampleFromFile
            k (process state sample)
    

    注意:

    w _ k state = getRandomSampleFromFile >>= k . process state
    

    所以

    myloop n = foldr (\ _ k state -> getRandomSampleFromFile >>= k . process state) return [1..n]
    

    之所以可行,是因为foldr的定义:

    foldr f z [] = z
    foldr f z (x:xn) = f x (foldr f z xn)
    

    : 的情况下,将递归调用放入一个thunk 和tail 调用 f,传递它那个thunk。您不必考虑这一点,因为在许多简单的折叠中,f 在其第二个参数中无论如何都是严格的(因此递归调用在f 的主体被输入之前有效地执行),但是foldr实际上立即将f 控制权交给它,并让它决定何时(如果有的话)执行递归调用。所以几乎任何递归结构都可以重写为foldr

    【讨论】:

    • this if "foldlM as foldr", not "`foldlM'` as foldr",可以这么说,并且可以观察到泄漏空间。尝试main = myloop (+) (readLn::IO Int) 1000000 0 &gt;&gt;= print 使用足够多的文本行。
    • 这可以通过使用$!来解决
    【解决方案4】:

    我想提供我的解决方案,因为这一直困扰着我一段时间。

    我们需要的是一个具有以下签名的函数:

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

    它应该认为iteratively m i 重复应用单子动作m 对先前动作的连续输出[1]。输出必须是一元动作数组的原因是我们只对n-th 单元动作感兴趣,它代表具有n 连续应用程序的动作。

    我得到的实现是这样的:

    iteratively step init = iterate (>>= step) (return init)
    

    现在,以 init 为初始值重复 n 次的操作 m 是 - 并且因此类似于您的 myloop

    repeatedly :: Monad m => (a -> m a) -> a -> Int -> m a
    repeatedly step init n = iteratively step init !! n
    

    [1]:m 这里表示将参数返回到下一个动作的单子动作——你称之为sample。它可以用getRandomSampleFromFileprocess 来实现,因此:

    process <$> getRandomSampleFromFile
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-27
      • 1970-01-01
      • 1970-01-01
      • 2021-08-03
      • 1970-01-01
      • 2014-04-13
      • 2011-06-15
      • 2013-07-12
      相关资源
      最近更新 更多