Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 我应该使用什么递归方案来重复有效的操作,直到其结果符合某个标准?_Haskell - Fatal编程技术网

Haskell 我应该使用什么递归方案来重复有效的操作,直到其结果符合某个标准?

Haskell 我应该使用什么递归方案来重复有效的操作,直到其结果符合某个标准?,haskell,Haskell,也就是说,我要问的是一个循环 effectful :: Int -> IO Int effectful n = do putStrLn $ "Effect: " ++ show n return n condition = (== 3) final :: Int -> IO () final n = putStrLn $ "Result: " ++ show n loop = ? 它应该是这样工作的: λ loop [1..10] Effect: 1 Effe

也就是说,我要问的是一个循环

effectful :: Int -> IO Int
effectful n = do
    putStrLn $ "Effect: " ++ show n
    return n

condition = (== 3)

final :: Int -> IO ()
final n = putStrLn $ "Result: " ++ show n

loop = ?
它应该是这样工作的:

λ loop [1..10]
Effect: 1
Effect: 2
Effect: 3
Result: 3
我可以提供一个递归定义:

loop (x: xs) = do
    r <- effectful x
    if condition r
       then final r
       else loop xs
-这让我想到:“如果我用
或者
来代替,然后打开
左侧的
结果会怎么样?”
这一思路让我想到了除
之外的
Control.Monad.,然后想到我应该
将期望的结果视为控制流中的例外。

exceptful :: Int -> ExceptT Int IO ()
exceptful n = do
    r <- lift (effectful n)
    if condition r
       then throwError r
       else return ()

loop' xs = fmap (fromRight ())
         $ runExceptT (traverse_ exceptful xs `catchError` (lift . final))
我认为这个解决方案很糟糕。首先,使用 左侧作为实际结果载体,第二,此代码比 递归的
循环
,我从它开始


可以做什么?

我喜欢将这类任务建模为包含有效流的函数。这个包在这方面很好,因为它提供了一个与传统的纯列表非常相似的api。(这就是说,
函子
/
应用程序
/
单子
实例有点不同:它们通过
“串联”工作,而不是像纯列表那样探索所有组合。)

例如:

import Streaming
import qualified Streaming.Prelude as S

loop :: Int -> (a -> Bool) -> IO a -> IO (Maybe a)
loop limit condition = S.head_ . S.filter condition . S.take limit . S.repeatM
使用“流媒体”中的、和函数

或者,如果我们有一个有效的函数和一个值列表:

loop :: (b -> Bool) -> (a -> IO b) -> [a] -> IO (Maybe b)
loop condition effectful = S.head_ . S.filter condition . S.mapM effectful . S.each
使用和来自“流”

如果我们希望执行最终有效的操作:

loop :: (b -> IO ()) -> (b -> Bool) -> (a -> IO b) -> [a] -> IO ()
loop final condition effectful = 
    S.mapM_ final . S.take 1 . S.filter condition . S.mapM effectful . S.each 

从“流”开始使用

有一门
base
课你忘了,我的朋友,那就是
Alternative
。考虑以下定义:

loop :: Alternative m => [m Int] -> m Int
loop = foldr (<|>) empty

effectful' :: Int -> IO Int
effectful' n = effectful n <* if condition n then return () else empty
loop::Alternative m=>[m Int]->m Int
loop=foldr()为空
有效'::Int->IO Int

effectful'n=effectful n您可以使用
whileM
:@WillemVanOnsem Awesome,为什么不在base中?@IgnatInsarov不清楚在标准库中定义5行或更少行的每个组合符有什么好处,而不是在需要时重新定义它。你必须在使用它编写代码和阅读代码时将其与查找的努力进行权衡。@IgnatInsarov:我个人在某种程度上喜欢
whileM
forM
与语言不是很“接近”。因为这有助于对问题进行更多的“声明式”思考。偶尔我会看到一些用户打算在Haskell中“翻译”命令式代码:他们使用
MVar
s来生成可变变量。虽然这可能会起作用,但它肯定不太“惯用”。。。然后。。。否则
是不必要的,可以使用。Haskell几乎没有真正的关键字(比如必须用语言定义的标记,并且不能通过库本身定义)。
()
for
IO
是危险的,因为它隐藏了异常。一些可能的简化:
foldr()empty
Data.Foldable.asum
<代码>如果条件n,则返回()否则为空
可以使用
Control.Monad.guard
实现。唉,如果没有元素满足条件,您的解决方案将抛出一个
IOException
IO
有一个奇怪的
备选
实例,其中
empty
抛出异常,
捕获第一个操作数抛出的异常。相关:@Li yaoXia很好地证明它只隐藏IO错误,这只是同步错误的一小部分,通常可以安全忽略。所以我看不出有什么危险。@IgnatInsarov危险在于,如果
有效
抛出一个IO异常——比如说,因为它试图打开一个文件但失败了——您在这里编写的循环将不会再次抛出该异常,但是相反,愉快地继续下一次迭代,就好像它仅仅是条件尚未满足的情况一样。
loop :: (b -> IO ()) -> (b -> Bool) -> (a -> IO b) -> [a] -> IO ()
loop final condition effectful = 
    S.mapM_ final . S.take 1 . S.filter condition . S.mapM effectful . S.each 
loop :: Alternative m => [m Int] -> m Int
loop = foldr (<|>) empty

effectful' :: Int -> IO Int
effectful' n = effectful n <* if condition n then return () else empty
λ loop (effectful' <$> [1..10]) >>= final
Effect: 1
Effect: 2
Effect: 3
Result: 3