是否有可能打破单子序列?
例如,如果我想根据在序列中间计算的某些条件更早地跳出序列。比如说,在“做”符号中,我绑定了一个值,并根据我想要完成序列或停止它的值。有类似“通过”功能的东西吗?
谢谢。
是否有可能打破单子序列?
例如,如果我想根据在序列中间计算的某些条件更早地跳出序列。比如说,在“做”符号中,我绑定了一个值,并根据我想要完成序列或停止它的值。有类似“通过”功能的东西吗?
谢谢。
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))
这对于根据您拥有的价值观简单地做一些不同的事情很有用。
正如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 一起工作,而不仅仅是 IO。它们与 IO 的异常具有相同的好处,因此在官方上很棒,但是您需要了解 monad 转换器。如果您的 monad 不是 IO,并且您有复杂的要求(如我对 Control.Exception 所说),请使用它们。
首先,阅读Gabriel Conzalez 的 Breaking from a loop以EitherT
根据出现的某些情况来做两件不同的事情,或者MaybeT
在出现问题时直接停在那里。
如果您对 Monad Transformers 一无所知,可以从Martin Grabmüller 的 Monad Transformers Step by Step开始。它涵盖ErrorT
. 之后再次阅读从循环中中断!
您可能还想阅读Real World Haskell 第 19 章,错误处理。
Continuation Passing StylecallCC
非常强大,但可能过于强大,而且肯定不会产生非常容易理解的代码。看到这个是一个相当积极的看法,这是一个非常消极的看法。
所以我认为你正在寻找的是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 函数。
但是根据 Ingo 的回答,我仍然在下面得到相同的效果,这很容易(我很傻)
doStuff x = if x > 5
then do
t <- getTingFromOutside
doHeavyHalculations t
else return ()
我不知道如果我需要在上面的示例中测试 't' 它将如何工作......我的意思是,如果我需要测试绑定值并从那里做出 if 决定。
根据定义,您永远无法摆脱“单子序列”。请记住,“单子序列”只不过是将一个函数应用于其他值/函数。即使“monad 序列”让您产生可以编写命令式编程的错觉,但事实并非如此(在 Haskell 中)!
你唯一能做的就是return ()
。实际问题的这种解决方案已在此处命名。但请记住:它只给你一种能够摆脱单子的错觉!