繁体   English   中英

循环一个monadic谓词

[英]Looping over a monadic predicate

对于循环函数,直到谓词成立为止

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 []

有更高阶函数可以简化这个吗? 也许和序列一起?

例如,您可以自己定义“monadic until”函数

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

让我们重构你的代码,直到我们到达这样一个组合器。

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 在此代码中,您从不在没有伴随return情况下调用readH 通过简单地删除不必要的返回, readH的参数实际上可以是纯粹的。 请注意,我们必须在then分支上添加return l ,并且不再需要在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成立。 事实上,它一直都有这种通用结构! 现在可以看到通用结构我们已经重命名了特定于任务的位。 通过制作takeOneStepcondition参数,我们得到了所需的组合子。

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缀,这使它看起来像一个do-while ,因此翻转的论点和“身体然后条件”胡说八道。

(do ...
    ...
) `untilM` someCondition

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM