如何使用高阶函数实现这种基于 IO 的循环?
How to implement this kind of IO-based loop using higher order functions?
我有一些类似下面的代码,它根据从磁盘读取的随机 sample
更新 state
:
myloop 0 state = return state
myloop n state = do
sample <- getRandomSampleFromFile
myloop (n - 1) (process state sample)
如何使用高阶函数以惯用的方式编写它来避免显式函数和递归(最好不要引入大量库)?
澄清:我不能只做一个replicateM n getRandomSampleFromFile
,因为我有大量的样本需要处理,首先将所有样本读入内存是不可行的。
来自
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)
无论如何,我鼓励使用 conduit
, pipes
,...如果您正在寻找一些流处理。
(顺便说一句,注意你的 getRandomSampleFromFile
函数可能有硬编码配置,这不好)
这应该提供一个提示:
> 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
是手头的样本。
一旦您理解了它的工作原理,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
.
我想提供我的解决方案,因为这已经困扰我一段时间了。
我们需要的是具有以下签名的函数:
iteratively :: Monad m => (a -> m a) -> a -> [m a]
并且应该认为 iteratively m i
将单子动作 m
重复应用于先前动作的连续输出 [1]。输出必须是单子动作数组的原因是我们只对第 n
个单子动作感兴趣,它表示具有 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
代表了returns下一个动作的参数的单子动作——你称之为sample
。它可以根据 getRandomSampleFromFile
和 process
来实现,因此:
process <$> getRandomSampleFromFile
我有一些类似下面的代码,它根据从磁盘读取的随机 sample
更新 state
:
myloop 0 state = return state
myloop n state = do
sample <- getRandomSampleFromFile
myloop (n - 1) (process state sample)
如何使用高阶函数以惯用的方式编写它来避免显式函数和递归(最好不要引入大量库)?
澄清:我不能只做一个replicateM n getRandomSampleFromFile
,因为我有大量的样本需要处理,首先将所有样本读入内存是不可行的。
来自
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)
无论如何,我鼓励使用 conduit
, pipes
,...如果您正在寻找一些流处理。
(顺便说一句,注意你的 getRandomSampleFromFile
函数可能有硬编码配置,这不好)
这应该提供一个提示:
> 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
是手头的样本。
一旦您理解了它的工作原理,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
.
我想提供我的解决方案,因为这已经困扰我一段时间了。
我们需要的是具有以下签名的函数:
iteratively :: Monad m => (a -> m a) -> a -> [m a]
并且应该认为 iteratively m i
将单子动作 m
重复应用于先前动作的连续输出 [1]。输出必须是单子动作数组的原因是我们只对第 n
个单子动作感兴趣,它表示具有 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
代表了returns下一个动作的参数的单子动作——你称之为sample
。它可以根据 getRandomSampleFromFile
和 process
来实现,因此:
process <$> getRandomSampleFromFile