如何在网络monad中"早日逃脱"

Sea*_*ess 6 haskell

网络编程时我遇到的事情很多:我想运行一个有可能失败的操作.在失败时,我想向客户端发送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
Run Code Online (Sandbox Code Playgroud)

因为错误是例外,我不喜欢我需要所有这些案例只是为了抓住它们.只要有任何东西,我想做同样的事情.有没有办法在一行上用类似的东西表达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)
}
Run Code Online (Sandbox Code Playgroud)

所以,我可以写一些样板文件,但是我不希望错误让我的嵌套变得混乱,因为只是想继续使用找到的案例更常见.

最干净的方法是什么?我在理论上知道我可以使用monad在失败时提前退出,但我只看到了一些示例,Maybe它会Nothing在最后返回,而不是让我指定它返回的内容.

Dan*_*ton 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
Run Code Online (Sandbox Code Playgroud)

以下是我用来确保所有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
Run Code Online (Sandbox Code Playgroud)

如果我这样做完全错了那么请有人喊出来.这可能是明智的,如果采取这种做法,修改的类型签名databaseCallanotherDatabaseCall也使用ErrorT,这样a <- ErrorT b可以减少到a <- bdoSomeWebStuff'.

由于我是一个完整的菜鸟ErrorT,除了"这里有一些代码,还有一些乐趣"之外,我不能真正做任何手工操作.


oza*_*man 5

不是你问题的直接答案,但是你考虑过使用Snap吗?在快照中,我们内置了一个惯用的短路行为:

getResponse >>= finishWith
Run Code Online (Sandbox Code Playgroud)

哪里

finishWith ::  MonadSnap m => Response -> m a
Run Code Online (Sandbox Code Playgroud)

因此,给定一个响应对象,它将提前终止(并匹配之后的任何类型).Haskell laziness将确保在finishWith之后不会执行Snap monad中的计算.

我有时会做一个小帮手:

finishEarly code str = do
  modifyResponse $ setResponseStatus code str
  modifyResponse $ addHeader "Content-Type" "text/plain"
  writeBS str
  getResponse >>= finishWith
Run Code Online (Sandbox Code Playgroud)

然后我可以在我的处理程序中的任何地方使用它.

myHandler = do
  x <- doSomething
  when (x == blah) $ finishEarly 400 "That doesn't work!!"
  doOtherStuff
Run Code Online (Sandbox Code Playgroud)