2012-01-27 8 views
6

Coś, co mi się przydarza podczas programowania stron internetowych: Chcę uruchomić operację, która ma szansę na niepowodzenie. W przypadku niepowodzenia chcę wysłać klientowi 500. Zazwyczaj jednak chcę kontynuować wykonywanie serii kroków.Jak "uciec wcześnie" w monadzie internetowej

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 

ponieważ błędy są wyjątkami, nie podoba mi się, że potrzebuję tych wszystkich rzeczy, żeby je złapać. Chcę zrobić to samo, ilekroć coś jest lewe. Czy istnieje sposób, aby wyrazić to w jednym wierszu z czymś takim jak guard, ale kontrolować, co zwraca na wyjściu?

w innym języku mogę to zrobić:

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

Tak, jestem ok pisząc jakiś boilerplate, ale nie chcę błędów zadzierać z moim gniazdowania, gdy jest znacznie bardziej powszechne, aby po prostu chcesz kontynuować dalej w znalezionym przypadku.

Jaki jest najczystszy sposób to zrobić? Wiem, że teoretycznie mogę użyć monady, aby wcześnie zakończyć awarię, ale widziałem tylko przykłady z Maybe i na końcu powrócą na koniec Nothing, zamiast pozwalać mi określić, co powróci.

+1

Piękny przykład dlaczego przepisy Monad są przydatne :) –

+0

Opcja 'Either' monada może być stosowany do wczesnego wyjścia z wartości zwracanej (' Left'). – shang

+1

@shang Obecnie 'Albo' '' '' 'jest' błędem', które prawdopodobnie dają kilka niepożądanych efektów.Polecam patrząc na 'ErrorT'. –

Odpowiedz

6

Oto, jak to zrobić z ErrorT. Zastrzeżenie: Nigdy wcześniej nie używałem 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 

Oto import i deklaracje typu I używane do upewnij się, że wszystkie typechecks poprawnie:

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 

Jeśli robię to wszystko źle to proszę, ktoś krzyczeć. Może być rozsądne, jeśli podejmiesz takie podejście, zmodyfikować sygnaturę typu databaseCall i anotherDatabaseCall, aby również użyć ErrorT, w ten sposób a <- ErrorT b można zredukować do a <- b w doSomeWebStuff'.

Ponieważ jestem kompletnym noobem w ErrorT, nie mogę zrobić żadnego trzymania ręki poza tym "tutaj jest kod, idź się zabawić".

5

Nie jest to bezpośrednia odpowiedź na Twoje pytanie, ale czy rozważałeś użycie Snap? W mgnieniu oka, mamy zwarcie zachowanie wbudowane wz idiomatyczne:

getResponse >>= finishWith 

gdzie

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

Więc dany obiekt odpowiedzi, będzie on zakończyć wcześnie (i mecz, niezależnie od typu przychodzi po to) . Lenistwo Haskella zapewni obliczenia w monecie Snap po zakończeniu. Nie zostanie wykonane.

czasami zarobić trochę pomocnika:

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

które można następnie używać w dowolnym miejscu w moich koparki.

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

+1 na pewno istotne; Zamierzałem wspomnieć w mojej odpowiedzi, że główne monady frameworków sieciowych prawdopodobnie mają wbudowaną formę ErrorT, ale nie znam się na tyle dobrze, aby uzasadnić to twierdzenie, tak jak ty. –

+0

Używam scotty, i rzeczywiście ma wykończenie podobne do konstrukcji –