2012-01-27 18 views
6

Algo que me sucede mucho durante la programación web: quiero ejecutar una operación que tenga una posibilidad de falla. En caso de falla, quiero enviar al cliente un 500. Normalmente, solo quiero seguir ejecutando una serie de pasos.Cómo "escapar temprano" en una mónada web

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 

dado que los errores son excepciones, no me gusta que necesite todo ese material para atraparlos. Quiero hacer lo mismo cada vez que quede algo. ¿Hay alguna manera de expresar eso en una línea con algo como guard, pero controlar lo que devuelve en una salida?

en otro idioma que podía hacer esto:

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) 
} 

Por lo tanto, yo estoy bien escribiendo algunos repetitivo, pero no quieren que los errores a meterse con mi anidación, cuando es mucho más común que simplemente quiero continuar con el caso encontrado.

¿Cuál es la forma más limpia de hacer esto? Sé en teoría que puedo usar una mónada para salir temprano de una falla, pero solo he visto ejemplos con Maybe y devolvería Nothing al final, en lugar de dejar que especifique qué devuelve.

+1

Un hermoso ejemplo de por qué leyes de las mónadas son útiles :) –

+0

El 'mónada Either' se puede utilizar para la salida temprano con un valor de retorno (' Left'). – shang

+1

@shang Hoy en día 'O bien el' error 'de e' es' error', es probable que produzca algunos efectos no deseados.Recomiendo ver 'ErrorT'. –

Respuesta

6

Así es como lo haría con ErrorT. Descargo de responsabilidad: nunca antes había usado 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 

Estas son las importaciones y las declaraciones de tipo que utiliza para asegurarse de que todos los typechecks correctamente:

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 

Si estoy haciendo esto mal, por favor, alguien gritar. Puede ser prudente, si toma este enfoque, modificar la firma tipo de databaseCall y anotherDatabaseCall para usar también ErrorT, de esa manera a <- ErrorT b se puede reducir a a <- b en doSomeWebStuff'.

Dado que soy un novato completo en ErrorT, no puedo hacer ningún otro tipo de mano además de "aquí hay un código, diviértete un poco".

5

No es una respuesta directa a su pregunta, pero ¿ha considerado usar Snap? En complemento, tenemos un cortocircuito en el comportamiento incorporado con un idiomática:

getResponse >>= finishWith 

donde

finishWith :: MonadSnap m => Response -> m a 

Así recibieron un objeto respuesta, se dará por terminada temprano (y combinar cualquier tipo viene después de eso) . La holgazanería de Haskell asegurará los cálculos dentro de la mónada de Snap después del final, sin que se ejecute.

a veces me hacen un poco de ayuda:

finishEarly code str = do 
    modifyResponse $ setResponseStatus code str 
    modifyResponse $ addHeader "Content-Type" "text/plain" 
    writeBS str 
    getResponse >>= finishWith 

que luego pueda usar en cualquier parte de mis manipuladores.

myHandler = do 
    x <- doSomething 
    when (x == blah) $ finishEarly 400 "That doesn't work!!" 
    doOtherStuff 
+1

+1 ciertamente relevante; Mencioné en mi respuesta que las mónadas principales del framework web probablemente tienen algún tipo de ErrorT incorporado, pero no estoy lo suficientemente familiarizado con ninguno para corroborar ese reclamo como usted. –

+0

Estoy usando scotty, y de hecho tiene un final. –