Haskell 如何使用高阶函数实现这种基于IO的循环?
我有一些类似以下的代码,它根据从磁盘读取的随机Haskell 如何使用高阶函数实现这种基于IO的循环?,haskell,higher-order-functions,Haskell,Higher Order Functions,我有一些类似以下的代码,它根据从磁盘读取的随机样本更新状态: myloop 0 state = return state myloop n state = do sample <- getRandomSampleFromFile myloop (n - 1) (process state sample) myloop 0状态=返回状态 myloop n state=do 样本 您可以使用应用程序语法 myloop n state = foldl process state <
样本
更新状态
:
myloop 0 state = return state
myloop n state = do
sample <- getRandomSampleFromFile
myloop (n - 1) (process state sample)
myloop 0状态=返回状态
myloop n state=do
样本
您可以使用应用程序语法
myloop n state = foldl process state <$> mapM (const getRandomSampleFromFile) [1..n]
myloop n state=foldl进程状态mapM(const getRandomSampleFromFile)[1..n]
或(thk2@andrás-kovács)
myloop n state=foldl进程状态replicItem 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=foldM acc state[1..n]
其中acc s 124; breakProcess s=返回s
|否则=进程的getRandomSampleFromFile
但是折叠不会停止,你最初的折叠(毕竟)看起来更好
myloop n state | breakProcess state = return state
| otherwise = do
x <- getRandomSampleFromFile
myloop (n - 1) (process state x)
myloop n状态| breakProcess状态=返回状态
|否则=做
这应该提供一个提示:
> 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)
所以
之所以这样做,是因为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]
它应该认为,迭代地mi
在先前动作的连续输出上重复应用一元动作m
[1]。输出必须是一个一元动作数组的原因是,我们只对表示具有n
连续应用程序的第n
个一元动作感兴趣
我达成的实施方案如下:
iteratively step init = iterate (>>= step) (return init)
现在,使用initail valueinit
重复了n次的动作m
是-因此类似于您的myloop
:
repeatedly :: Monad m => (a -> m a) -> a -> Int -> m a
repeatedly step init n = iteratively step init !! n
[1] :m
这里表示将参数返回到下一个操作的一元操作-您称之为sample
。它可以通过getRandomSampleFromFile
和process
实现,因此:
process <$> getRandomSampleFromFile
处理getRandomSampleFromFile
我在原始问题中没有指出这一点,但这是否意味着我必须首先读取磁盘上的所有随机样本?这对于我的用例是不可行的。mapM(const x)[1..n]=replictem nx
我不确定replictem是否必须读取内存中的所有样本。我可能错了。@mb14当然会的ReplicatItem
像sequence
,traverse
和mapM
只有在将列表视为数组的情况下才有意义。你也可以使用一个真正的数组类型,比如说vector
,当你看到这些单词时,想想“可能是流媒体库?”。在长列表上,它们总是会累积列表并导致空间泄漏。这就是为什么我们有流媒体库。(例如,可以将replicItem
&公司专门化为IO,然后使用类似惰性IO的东西,请参见)如果“foldlM
作为foldr
”,而不是“foldlM”`asfoldr
”,可以观察到空间泄漏。请尝试使用足够的文本行打印main=myloop(+)(readLn::IO Int)1000000>=这可以通过使用$解决代码>
myloop n = foldr (\ _ k state -> getRandomSampleFromFile >>= k . process state) return [1..n]
foldr f z [] = z
foldr f z (x:xn) = f x (foldr f z xn)
iteratively :: Monad m => (a -> m a) -> a -> [m a]
iteratively step init = iterate (>>= step) (return init)
repeatedly :: Monad m => (a -> m a) -> a -> Int -> m a
repeatedly step init n = iteratively step init !! n
process <$> getRandomSampleFromFile