打破monad序列

时间:2013-07-16 13:30:07

标签: haskell

是否有可能打破monad序列?

例如,如果我想根据在序列中间计算的某些条件先断开序列。比如,在'do'表示法中,我绑定一个值,并根据我想要完成序列或停止它的值。是否有类似“通行证”的功能?

感谢。

4 个答案:

答案 0 :(得分:9)

直接使用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

我真的应该滚动我自己的自定义类型,但我不能打扰派生类型等,所以我会用标准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's Breaking from a loop使用EitherT根据某些条件执行两项不同的操作,或MaybeT在发生问题时立即停在那里。

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

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

呼叫/ CC

Continuation Passing Style callCC非常强大,但可能过于强大,当然也不会产生非常容易理解的代码。请参阅this以获得相当积极的评价,this获得非常消极的评价。

答案 1 :(得分:2)

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

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

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

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

我们不能只是“停止”在中间应用东西。幸运的是,如果你真的需要它,Cont monad会得到答案。 callCC。这是“使用当前继续调用”的缩写,并概括了返回的符号。如果你知道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是一个简单的旧功能,完全不具有魔力,实施它是一个很大的挑战

答案 2 :(得分:1)

所以我认为没有办法像我原来想象的那样去做,这相当于命令循环中的中断函数。

但是根据Ingo的回答我仍然得到同样的效果,这很容易(愚蠢的我)

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

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

答案 3 :(得分:0)

根据定义,你永远不会打破“monad序列”。请记住,“monad序列”只不过是应用于其他值/函数的一个函数。即使“monad序列”给你一种你可以编程命令的错觉,但事实并非如此(在Haskell中)!

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