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几乎没有真正的关键字(比如必须用语言定义的标记,并且不能通过库本身定义)。()
forIO
是危险的,因为它隐藏了异常。一些可能的简化: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