是否有可能打破monad序列?
例如,如果我想根据在序列中间计算的某些条件先断开序列。比如,在'do'表示法中,我绑定一个值,并根据我想要完成序列或停止它的值。是否有类似“通行证”的功能?
感谢。
答案 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))
这对于根据你拥有的价值做一些不同的事情是有好处的。
您可以将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,而不仅仅是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。
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 ()
。 实用问题的解决方案已在此处命名。但请记住:它只给你一种能够打破单子的错觉!