4

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

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

谢谢。

4

4 回答 4

9

直接使用if

您可以直接将Ingo 封装为精美封装,或者等效地执行此操作

    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 中使用异常

正如Michael Litchard 正确指出的那样,您可以使用Control.Exception。它有大量的错误处理、控制流改变的东西,如果你想用它做一些复杂的事情,值得一读。

如果您的错误生产可能发生在任何地方并且您的代码很复杂,那么这很好。您可以在顶层或您喜欢的任何级别处理错误。它非常灵活,不会与您的返回类型混淆。它仅适用于 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. 我已经提出了让街区排在最后flip的论点。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 的 Breaking from a loopEitherT根据出现的某些情况来做两件不同的事情,或者MaybeT在出现问题时直接停在那里。

如果您对 Monad Transformers 一无所知,可以从Martin Grabmüller 的 Monad Transformers Step by Step开始。它涵盖ErrorT. 之后再次阅读从循环中中断!

您可能还想阅读Real World Haskell 第 19 章,错误处理

致电/抄送

Continuation Passing StylecallCC非常强大,但可能过于强大,而且肯定不会产生非常容易理解的代码。看到这个是一个相当积极的看法,是一个非常消极的看法。

于 2013-07-16T14:15:49.247 回答
2

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

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

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

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

我们不能只是在中间“停止”应用东西。幸运的是,如果你真的需要它,Contmonad 会给出答案。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这是一个普通的旧功能并且完全没有魔法,实现它是一个巨大的挑战

于 2013-07-16T13:36:17.870 回答
1

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

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

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

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

于 2013-07-16T15:24:08.470 回答
0

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

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

于 2013-07-16T14:39:23.030 回答