【问题标题】:Breaking out of monad sequence打破单子序列
【发布时间】:2013-07-14 17:09:09
【问题描述】:

是否有可能打破单子序列?

例如,如果我想根据在序列中间计算的某些条件更早地跳出序列。比如说,在“do”符号中,我绑定了一个值,并根据我想要完成序列或停止它的值。有没有类似“通过”的功能?

谢谢。

【问题讨论】:

  • 您可以引发异常。查看 Control.Exception。
  • 这里的“一元序列”到底是什么意思?您的意思是手动编写的do 块,或者可能是一些循环的动作,例如mapM_?
  • @Michael Litchard 这正是我的想法。如果我在序列中间粘贴类似错误“我离开这里......”的内容,那么它会给我我想要的行为。
  • @leftaroundabout 是的,在一个“do”块中说我想早点退出。就像把误差函数放在中间的效果一样。
  • 甜蜜!我不确定你想要什么,但似乎有例外。

标签: haskell


【解决方案1】:

直接使用if

您可以直接以Ingo beautifully encapsulated 执行此操作,或者等效地例如

    breakOut :: a -> m (Either MyErrorType MyGoodResultType)
    breakOut x = do
        y <- dosomethingWith x
        z <- doSomethingElseWith x y
        if isNoGood z then return (Left (someerror z)) else do
            w <- process z
            v <- munge x y z
            u <- fiddleWith w v
            return (Right (greatResultsFrom u z))

这很适合根据你的价值观做一些不同的事情。

在 IO monad 中使用异常

您可以将Control.Exception 用作Michael Litchard correctly pointed out。它有大量的错误处理、控制流改变的东西,如果你想用它做一些复杂的事情,值得一读。

如果您的错误产生可能发生在任何地方并且您的代码很复杂,那么这非常好。您可以在顶层或您喜欢的任何级别处理错误。它非常灵活,不会与您的返回类型混淆。它只适用于 IO monad。

import Control.Exception

真的我应该推出我自己的自定义类型,但我不会为派生 Typable 等而烦恼,所以我将使用标准的 error 函数和一些字符串来破解它。我对此感到非常内疚。

handleError :: ErrorCall -> IO Int
handleError (ErrorCall msg) = case msg of
   "TooBig" -> putStrLn "Error: argument was too big" >> return 10000
   "TooSmall" -> putStrLn "Error: argument was too big" >> return 1
   "Negative" -> putStrLn "Error: argument was too big" >> return (-1)
   "Weird" -> putStrLn "Error: erm, dunno what happened there, sorry." >> return 0

错误处理程序需要在catch 中使用显式类型。我已经 flipped 参数让 do 块排在最后。

exceptOut :: IO Int
exceptOut = flip catch handleError $ do
     x <- readLn
     if (x < 5) then error "TooSmall" else return ()
     y <- readLn
     return (50 + x + y)

Monad 转换器等

这些设计用于任何 monad,而不仅仅是 IO。它们与 IO 的异常具有相同的好处,因此在官方上很棒,但是您需要了解 monad 转换器。如果您的 monad 不是 IO,并且您有复杂的要求(如我对 Control.Exception 所说),请使用它们。

首先,阅读Gabriel Conzalez's Breaking from a loop 了解使用EitherT 来根据出现的某些情况做两件不同的事情,或者阅读MaybeT 了解在出现问题时直接停在那里。

如果您对 Monad Transformers 一无所知,可以从Martin Grabmüller's Monad Transformers Step by Step 开始。它涵盖ErrorT。之后再次阅读 Breaking from a Loop!

您可能还想阅读Real World Haskell chapter 19, Error handling

致电/抄送

Continuation Passing Style 的callCC 非常强大,但也可能过于强大,而且肯定不会产生非常易于理解的代码。请参阅this 以获得相当积极的看法,this 获得非常消极的看法。

【讨论】:

  • 说实话,我不确定它是否漂亮。而且我通常更喜欢你在那里展示的明确风格,因此你得到了我的 +1。
  • @Ingo :D 我一看到你的组合器风格就喜欢它,所以我赞成你的!
  • 仍然不确定 x 是否应该更好地放在最后,以便您可能会说something &gt;&gt;= pass ifOk (do ...)
  • @Ingo 那会很整洁,是的。
  • 如果使用了 EitherT monad 转换器,这段代码可以变得更简洁——尤其是返回部分。以必须在这里和那里提供几部电梯的(潜在)成本。
【解决方案2】:

根据定义,您永远无法摆脱“单子序列”。请记住,“monad 序列”只不过是应用于其他值/函数的 一个 函数。即使“monad 序列”让您产生可以编写命令式编程的错觉,但事实并非如此(在 Haskell 中)!

你唯一能做的就是return ()实际问题的解决方案已在此处命名。但请记住:它只会给你一种能够摆脱单子的错觉!

【讨论】:

  • 注意:return 并没有像在命令式编程中那样做任何特殊的事情,它只是将输入作为输出。
【解决方案3】:

所以我想没有办法按照我最初想象的方式来做,这相当于命令式循环中的中断函数。

但是根据 Ingo 的回答,我仍然得到以下相同的效果,这很容易(我很傻)

doStuff x = if x > 5
            then do
                 t <- getTingFromOutside
                 doHeavyHalculations t
             else return ()

如果我需要在上面的示例中测试“t”,我不知道它会如何工作...... 我的意思是,如果我需要测试绑定值并从那里做出 if 决定。

【讨论】:

  • 没关系 - 您可以再次执行相同的操作,然后添加一行 if (t &gt;1000) then return () else doHeavyCalculations t 而不仅仅是 doheavyCalculations
【解决方案4】:

所以我认为您正在寻找的是命令式语言中的return 等价物,例如

def do_something
  foo
  bar
  return baz if quux
  ...
end

现在在 haskell 中这是行不通的,因为一元链只是一个大函数应用程序。我们有使它看起来更漂亮的语法,但它可以写成

bind foo (bind bar (bind baz ...)))

我们不能只是“停止”在中间应用东西。幸运的是,如果您真的需要它,Cont monad 会提供答案。 callCC。这是“call with current continuation”的缩写,概括了回报的表示法。如果你知道 Scheme,那么应该很熟悉。

import Control.Monad.Cont

foo = callCC $ \escape -> do
   foo
   bar
   when baz $ quux >>= escape
   ...

从 Control.Monad.Cont 的文档中无耻窃取的可运行示例

whatsYourName name =
  (`runCont` id) $ do
    response <- callCC $ \exit -> do
      validateName name exit
      return $ "Welcome, " ++ name ++ "!"
    return response

validateName name exit = do
  when (null name) (exit "You forgot to tell me your name!")

当然,还有一个Cont 转换器,ContT(这绝对是令人费解的),它可以让你在 IO 或其他东西上分层。

顺便说一句,callCC 是一个普通的旧函数,完全没有魔法,实现它是一个巨大的挑战

【讨论】:

  • 谢谢。我正在寻找命令式循环中 break 函数的效果。
  • @r.sendecky 这里可以通过callCC+递归实现同样的效果
  • 实现callCC 可以仅从类型自动完成。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 2022-12-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多