对于循环函数,直到谓词成立为止
until :: (a -> Bool) -> (a -> a) -> a -> a
然而,一旦谓词具有形式
,这就会失败Monad m => (a -> m b)
我发现的唯一方法是通过显式递归,例如从句柄中读取,直到达到EOF
:
(_, (Just stdout), _, _) <- createProcess (proc "task" (args fl)){ std_out = CreatePipe }
let readH :: IO [Either String Task] -> IO [Either String Task]
readH l = do eof <- hIsEOF stdout
if eof
then l
else do line <- hGetLine stdout
l' <- l
readH.return $ (eitherDecodeStrict' line) : l'
out <- readH $ return []
是否有更高阶的功能简化了这一点?也许和sequence一起?
答案 0 :(得分:4)
你可以定义一个&#34; monadic直到&#34;自己发挥作用,例如
untilM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
untilM p f = go
where
go x = do r <- p x
if r
then return x
else do a <- f x
go a
或者,如果您的谓词不需要参数,
untilM :: Monad m => m Bool -> (a -> m a) -> a -> m a
untilM p f = go
where
go x = do r <- p
if r
then return x
else do a <- f x
go a
甚至,你根本不想要任何论据,
untilM :: Monad m => m Bool -> m a -> m ()
untilM p f = do r <- p
if r
then return ()
else do f
untilM p f
答案 1 :(得分:3)
让我们重构你的代码,直到我们到达这样一个组合器。
let readH :: IO [Either String Task] -> IO [Either String Task]
readH l = do eof <- hIsEOF stdout
if eof
then l
else do line <- hGetLine stdout
l' <- l
readH.return $ (eitherDecodeStrict' line) : l'
out <- readH $ return []
首先,我想指出多余的return
。在此代码中,如果没有附带的readH
,则永远不会致电return
。 readH
的参数实际上可以通过简单地删除不必要的返回来实现。请注意,我们必须在return l
分支上添加then
,而不再需要在else分支上“执行”l' <- l
。
let readH :: [Either String Task] -> IO [Either String Task]
readH l = do eof <- hIsEOF stdout
if eof
then return l
else do line <- hGetLine stdout
readH $ (eitherDecodeStrict' line) : l
out <- readH []
好的,现在我要重新命名一些内容以保持清晰并略微重新格式化。
let -- how to check the stop condition
condition :: IO Bool
condition = hIsEOF stdout
let -- what IO to do at each iteration
takeOneStep :: IO ByteString
takeOneStep = hGetLine stdout
let -- what pure work to do at each iteration
pureTransform :: ByteString -> Either String Task
pureTransform = eitherDecodeStrict'
let readH :: [Either String Task] -> IO [Either String Task]
readH theRest = do
isDone <- condition
if isDone
then return theRest
else do
raw <- takeOneStep
readH (pureTransform raw : theRest)
out <- readH []
确保您了解此版本的代码与上一版本的相同之处;它只是将一些表达式重命名并排除在外。
pureTransform
在这里有点红鲱鱼。我们可以将其与takeOneStep
捆绑在一起。
let -- how to check the stop condition
condition :: IO Bool
condition = hIsEOF stdout
let -- what IO to do at each iteration
takeOneStep :: IO (Eiter String Task)
takeOneStep = do
line <- hGetLine stdout
return (eitherDecodeStrict' line)
let readH :: [Either String Task] -> IO [Either String Task]
readH theRest = do
isDone <- condition
if isDone
then return theRest
else do
thisStep <- takeOneStep
readH (thisStep : theRest)
out <- readH []
此时重新阅读readH
的正文。请注意,它不再是特定于此特定任务的。它现在描述了takeOneStep
的一般循环,直到condition
成立。事实上,它一直都有这种通用结构!现在可以看到通用结构我们已经重命名了特定于任务的位。通过创建函数的takeOneStep
和condition
个参数,我们得到了所需的组合子。
untilIO :: IO Bool -> IO (Either String Task) -> [Either String Task] -> IO [Either String Task]
untilIO condition takeOneStep theRest = do
isDone <- condition
if isDone
then return theRest
else do
thisStep <- takeOneStep
untilIO (thisStep : theRest)
请注意,此实现的组合子不必限制为Either String Task
;它适用于任何类型a
,而不是Either String Task
。
untilIO :: IO Bool -> IO a -> [a] -> IO [a]
请注意,实现的组合器甚至不必限制为IO
。它适用于任何Monad m
而不是IO
。
untilM :: Monad m => m Bool -> m a -> [a] -> m [a]
故事的寓意是这样的:通过为特定用例通过显式递归来计算如何编写“循环一个monadic谓词”,你已经编写了一般的组合子!它就在你的代码结构中,等待被发现。
有几种方法可以进一步清理,例如删除[]
参数并按顺序构建列表(目前列表反转,你会注意到),但这些超出了我现在想要做的一点,所以留给读者练习。假设你已经完成了这两件事,你最终会得到
untilM :: m Bool -> m a -> m [a]
我会在你的例子中使用它,如下所示:
(_, (Just stdout), _, _) <- createProcess (proc "task" (args fl)){ std_out = CreatePipe }
out <- untilM (hIsEof stdout) $ do
line <- hGetLine stdout
return (eitherDecodeStrict' line)
看起来很像命令式“直到”循环!
如果你交换参数顺序,那么你最终得到的东西几乎相当于Control.Monad.Loops.untilM
。请注意,与我们的解决方案不同,Control.Monad.Loops.untilM
(恼人地!)总是在检查条件之前执行操作,因此如果您可能正在处理空文件,则在这种情况下使用它并不十分安全。他们显然希望你使用untilM
infix,这使它看起来像do-while
,因此翻转的参数和“body then condition”无意义。
(do ...
...
) `untilM` someCondition