网络编程时我遇到的事情很多:我想运行一个有可能失败的操作。在失败时,我想向客户端发送500.通常,我只想继续执行一系列步骤。
doSomeWebStuff :: SomeWebMonad ()
doSomeWebStuff = do
res <- databaseCall
case res of
Left err -> status 500
Right val -> do
res2 <- anotherDatabaseCall (someprop val)
case res2 of
Left err -> status 500
Right val2 -> text $ show val2
因为错误是例外,我不喜欢我需要所有这些案例只是为了抓住它们。只要有任何东西,我想做同样的事情。有没有办法在一行中用guard
来表达,但是控制它在退出时返回的内容?
用另一种语言我可以这样做:
function doSomeWebStuff() {
var res = databaseCall()
if (res == Error) return status 500
var res2 = anotherDatabaseCall(res.someprop)
if (res2 == Error) return status 500
return text(res2)
}
所以,我可以写一些样板文件,但我不希望错误让我的嵌套变得混乱,因为只是想继续使用找到的案例更常见。
最干净的方法是什么?我在理论上知道我可以使用monad在失败时提前退出,但我只看到Maybe
的示例,并且最后会返回Nothing
,而不是让我指定返回的内容。
答案 0 :(得分:6)
以下是我如何使用ErrorT
执行此操作。免责声明:我之前从未真正使用ErrorT
。
webStuffOr500 :: ErrorT String SomeWebMonad () -> SomeWebMonad ()
webStuffOr500 action = do
res <- runErrorT action
case res of
Left err -> do
logError err -- you probably want to know what went wrong
status 500
Right () -> return ()
doSomeWebStuff :: SomeWebMonad ()
doSomeWebStuff = webStuffOr500 doSomeWebStuff'
doSomeWebStuff' :: ErrorT String SomeWebMonad ()
doSomeWebStuff' = do
val <- ErrorT databaseCall
val2 <- ErrorT $ anotherDatabaseCall (someprop val)
lift $ text $ show val2
以下是我用来确保所有typechecks正确的导入和类型声明:
import Control.Monad.Identity
import Control.Monad.Error
import Control.Monad.Trans (lift)
import Control.Monad
type SomeWebMonad = Identity
data Foo = Foo
data Bar = Bar
data Baz = Baz deriving (Show)
someprop :: Foo -> Bar
someprop = undefined
databaseCall :: SomeWebMonad (Either String Foo)
databaseCall = undefined
anotherDatabaseCall :: Bar -> SomeWebMonad (Either String Baz)
anotherDatabaseCall = undefined
logError :: String -> SomeWebMonad ()
logError = undefined
text :: String -> SomeWebMonad ()
text = undefined
status :: Int -> SomeWebMonad ()
status = undefined
如果我这样做完全错了那么请有人喊出来。如果采用这种方法,修改databaseCall
和anotherDatabaseCall
的类型签名以使用ErrorT
可能是明智的,a <- ErrorT b
可以简化为{{a <- b
1}} doSomeWebStuff'
。
由于我是ErrorT
的完整菜鸟,除了“这里有一些代码,还有一些乐趣”之外,我无法真正做任何手持。
答案 1 :(得分:5)
不能直接回答您的问题,但您是否考虑过使用Snap?在快照中,我们内置了一个惯用的短路行为:
getResponse >>= finishWith
其中
finishWith :: MonadSnap m => Response -> m a
因此,给定一个响应对象,它将提前终止(并匹配之后的任何类型)。 Haskell laziness将确保在finishWith之后Snap monad中的计算不会被执行。
我有时会做一个小帮手:
finishEarly code str = do
modifyResponse $ setResponseStatus code str
modifyResponse $ addHeader "Content-Type" "text/plain"
writeBS str
getResponse >>= finishWith
然后我可以在我的处理程序中的任何地方使用。
myHandler = do
x <- doSomething
when (x == blah) $ finishEarly 400 "That doesn't work!!"
doOtherStuff