如何在网络monad中“早日逃脱”

时间:2012-01-27 13:49:10

标签: 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

因为错误是例外,我不喜欢我需要所有这些案例只是为了抓住它们。只要有任何东西,我想做同样的事情。有没有办法在一行中用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,而不是让我指定返回的内容。

2 个答案:

答案 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

如果我这样做完全错了那么请有人喊出来。如果采用这种方法,修改databaseCallanotherDatabaseCall的类型签名以使用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