【问题标题】:Stateful loop with different types of breaks具有不同类型中断的有状态循环
【发布时间】:2015-09-04 06:59:00
【问题描述】:

我正在尝试将以下有状态命令式代码转换为 Haskell。

while (true) {
  while (get()) {
    if (put1()) {
      failImmediately();
    }
  }
  if (put2()) {
    succeedImmediately();
  }
}

put1put2 都读取系统的状态并对其进行修改。 get 可以为简单起见读取状态。 failImmediately 应该跳出无限循环并呈现一种结果,succeedImmediately 也应该跳出但呈现不同的结果。

我尝试使用的是State Env Result,其中Env 表示环境状态,Result 类似于Either Failure Success,用于某些自定义FailureSuccess

一旦产生其中一个(打破循环),整个结果表达式应该折叠到Failure/Success 中,否则继续运行。

我的一个想法是使用Either Exit () where data Exit = Success | Failure 并使用StateT 以某种方式对EitherLeft 进行操作,就好像Either 是被链接的monad,即忽略任何后续操作。

如果能获得与上述 sn-p 相同的行为,我将非常感谢任何灵感或 Haskell 代码示例。

编辑:改进版移至单独的问题“Stateful computation with different types of short-circuit (Maybe, Either)”。

【问题讨论】:

  • 您应该查看EitherT (State Env Result)。让我知道该提示是否不够,您需要更多详细信息:)
  • 我觉得这可能是我需要的,但我完全不知道如何在这种情况下使用它:(。如果你能详细说明我会永远如此感激不尽。

标签: haskell monads monad-transformers state-monad


【解决方案1】:

使用@chi 的答案中的套件,只是强调您不需要ContT 的全部功能,EitherT 的直接短路语义就足够了:

import Control.Monad.Trans.Either

data Result a = Failure | Success a

foo :: EitherT (Result Int) IO Int
foo = forever $ do
    whileM get $ do
        whenM put1 $ do
            left Failure
    whenM put2 $ do
        left $ Success 42

run :: (Monad m) => EitherT (Result a) m a -> m (Maybe a)
run act = do
    res <- runEitherT act
    return $ case res of
        Left Failure -> Nothing
        Left (Success x) -> Just x
        Right x -> Just x

-- whenM / whileM and get/put1/put2 as per @chi's answeer

【讨论】:

  • 我同意——ContT 有点过分了。
  • 谢谢,这似乎解决了我原来的问题(当我用 State 替换 IO 时)。考虑到我原始帖子的“编辑”,难道不是更简单吗?
  • 可能有一个基于MonadPlus 的解决方案...也许值得一个单独的问题(问题范围缩小)
【解决方案2】:

几乎是字面意思,不优雅但有效的翻译。

我们利用ContT monad 转换器来实现 “早日归来”。即,我们希望能够在任何时候打破我们的循环。这是通过使用callCC $ \exit -&gt; ... 来实现的,这大致使exit 成为我们的神奇函数,让我们立即逃离内部块。

import Control.Monad.Cont

action :: IO String
action = flip runContT return $ callCC $ \exit -> 
   forever $ do                   -- while (true)
      let loop = do
             r1 <- lift $ get     -- if (get())
             when r1 $ do
                r2 <- lift $ put1
                when r2 $         -- if (put1())
                   exit "failImmediately"
                loop              -- "repeat while"
      loop
      r3 <- lift $ put2
      when r3 $
         exit "succeedImmediately"

get :: IO Bool
get = readLn

put1 :: IO Bool
put1 = putStrLn "put1 here" >> readLn

put2 :: IO Bool
put2 = putStrLn "put2 here" >> readLn

main :: IO ()
main = action >>= putStrLn

我们还可以定义一些自定义助手来美化代码:

action2 :: IO String
action2 = flip runContT return $ callCC $ \exit -> 
   forever $ do                -- while (true)
      whileM get $             -- while(get())
         whenM put1 $          -- if (put1())
            exit "failImmediately"
      whenM put2 $             -- if (put2())
         exit "succeedImmediately"

whenM :: (MonadTrans t, Monad m, Monad (t m)) => m Bool -> t m () -> t m ()
whenM condition a = do
   r <- lift condition
   when r a

whileM :: (MonadTrans t, Monad m, Monad (t m)) => m Bool -> t m () -> t m ()
whileM condition a = whenM condition (a >> whileM condition a)

【讨论】:

  • 谢谢,我希望有一种方法可以将所有类似控制流的东西(何时)隐藏在某种抽象中(因为可能/或者单子隐藏)而且这两个结果的类型也不同,此示例假定两个结果都是 IO 字符串。我希望整个事情要么成功要么失败,或者永远不会终止。
  • 如果结果是不同的类型,您可以使用Either A B 而不是String,这样您就可以在经过Left/Right 包装后将其用作通用类型。您可能已经意识到这一点。
  • 可能有一些更优雅的方法可以使用某些库中的一些巧妙的循环组合器来抽象所有内容。然而,代码是非常命令式和有状态的:每一行都会以某种方式影响状态。命令式 sn-p 也很简单——我不知道我们是否能在清晰度方面打败它。也许其他人会提出更好的解决方案。
  • @JakubDaniel 我也意识到你只需要State 而不是IO
猜你喜欢
  • 1970-01-01
  • 2016-09-05
  • 2015-11-30
  • 2017-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-22
  • 1970-01-01
相关资源
最近更新 更多