2012-01-27 12 views
6

Qualcosa che mi succede molto durante la programmazione web: voglio eseguire un'operazione che ha una possibilità di fallimento. In caso di fallimento, voglio inviare al client un 500. Normalmente, però, voglio solo continuare a eseguire una serie di passaggi.Come "scappare presto" in una web monad

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 

poiché gli errori sono eccezioni, non mi piace il fatto che ho bisogno di tutto quel materiale per catturarli. Voglio fare la stessa cosa ogni volta che è una sinistra. C'è un modo per esprimerlo su una riga con qualcosa come guard, ma controllare cosa restituisce in uscita?

in un'altra lingua ho potuto fare questo:

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

Quindi, sto bene a scrivere un po 'di testo standard, ma non voglio gli errori a pasticciare con la mia nidificazione, quando è molto più comune di appena voglio continuare con il caso trovato.

Qual è il modo più pulito per farlo? So che in teoria posso usare una monade per uscire presto in caso di errore, ma ho visto solo esempi con Maybe e restituirei Nothing alla fine, piuttosto che permettermi di specificare cosa restituisce.

+1

Un bellissimo esempio del perché le leggi Monade sono utili :) –

+0

La monade 'Either' può essere utilizzato per l'uscita anticipata con un valore di ritorno (' Left'). – shang

+1

@shang Al giorno d'oggi, 'o''s' fail' è' error', è probabile che fornisca alcuni effetti indesiderati.Consiglio di guardare "ErrorT'. –

risposta

6

Ecco come lo farei con ErrorT. Disclaimer: non ho mai effettivamente usato ErrorT prima.

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 

Ecco le importazioni e le dichiarazioni di tipo ho usato per fare correttamente sicuro che tutti 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 

Se io sto facendo questo tutto sbagliato allora per favore, qualcuno gridare. Può essere saggio, se si utilizza questo approccio, modificare la firma del tipo di databaseCall e anotherDatabaseCall per utilizzare anche ErrorT, in questo modo a <- ErrorT b può essere ridotto a a <- b in doSomeWebStuff'.

Dal momento che sono un noob completo allo ErrorT, non riesco davvero a tenere tutto in mano oltre "Ecco un po 'di codice, divertiti un po'".

5

Non è una risposta diretta alla tua domanda, ma hai considerato l'utilizzo di Snap? Nel gioco da ragazzi, abbiamo cortocircuitando comportamento built-in con una idiomatica:

getResponse >>= finishWith 

dove

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

Quindi, dato un oggetto di risposta, terminerà presto (e abbinare qualsiasi tipo viene dopo che) . La pigrizia di Haskell assicurerà i calcoli entro la monade Snap dopo la fine. Non sarà eseguito.

volte mi fanno un po 'di aiuto:

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

che posso utilizzare ovunque nei miei gestori.

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

+1 certamente rilevanti; Nella mia risposta stavo per menzionare che le principali monade del framework web probabilmente hanno una qualche forma di ErrorT integrato, ma non ho familiarità con nessuna delle altre a sostegno di tale affermazione come lei. –

+0

Sto usando scotty e, in effetti, ha una finitura. –