2012-12-20 11 views
9

Przesyłam aplikację Java do Haskell. Główną metodą aplikacji Java następujący wzór:Jak wdrożyć wcześniejsze wyjście/powrót w Haskell?

public static void main(String [] args) 
{ 
    if (args.length == 0) 
    { 
    System.out.println("Invalid number of arguments."); 

    System.exit(1); 
    } 

    SomeDataType d = getData(arg[0]); 
    if (!dataOk(d)) 
    { 
    System.out.println("Could not read input data."); 

    System.exit(1); 
    } 

    SomeDataType r = processData(d); 
    if (!resultOk(r)) 
    { 
    System.out.println("Processing failed."); 

    System.exit(1); 
    } 

    ... 
} 

Więc mam różne kroki i po każdym etapie mogę albo wyjście z kodem błędu lub przejść do następnego kroku.

Moja próba Porting to Haskell jest następujący:

main :: IO() 
main = do 
     a <- getArgs 
     if (null args) 
      then do 
        putStrLn "Invalid number of arguments." 
        exitWith (ExitFailure 1) 
      else do 
        -- The rest of the main function goes here. 

Dzięki takiemu rozwiązaniu, nie będę miał wiele zagnieżdżonych if-then-else (po jednym dla każdego punktu wyjścia oryginalnego kodu Java).

Czy istnieje bardziej elegancki/idiomatyczny sposób realizacji tego wzoru w Haskell? Ogólnie rzecz biorąc, jaki jest Haskell idiomatyczny sposób na wdrożenie wczesnego wyjścia/powrotu, stosowanego w imperatywnym języku, takim jak Java?

+1

Proszę przeczytać przykład Spacerując po linii ze strony http://learnyouahaskell.com/a-fistful-of-monads. Podaje przykład Monady używającej Może typ. Gdy wynik jakiegokolwiek wyrażenia jest Niczym, wynikiem wszystkich następujących wyrażeń jest Nic, tak dobry jak wyszedłeś w punkcie awarii. –

+0

@ManojR - Może nie jest to tutaj odpowiednie, ponieważ również chcesz podać przyczynę niepowodzenia. – Lee

+0

Klasycznym podejściem jest podzielenie procesu na "ufającą" funkcję przetwarzania (która zakłada, że ​​parametry są poprawne) i "paranoidalną" funkcję sprawdzania poprawności (która sprawdza tylko, czy parametry są poprawne) ... –

Odpowiedz

6

Nieco bardziej sensowne podejście w Haskell, który wykorzystuje ten sam rodzaj logiki warunkowej próbowano może wyglądać następująco:

fallOverAndDie :: String -> IO a 
fallOverAndDie err = do putStrLn err 
         exitWith (ExitFailure 1) 

main :: IO() 
main = do a <- getArgs 
      case a of 
       [d] | dataOk d -> doStuff $ processData d 
        | otherwise -> fallOverAndDie "Could not read input data." 
       _ -> fallOverAndDie "Invalid number of arguments." 


processData r 
    | not (resultOk r) = fallOverAndDie "Processing failed." 
    | otherwise  = do -- and so on... 

W tym konkretnym przypadku, biorąc pod uwagę, że exitWith kończy program zresztą mogliśmy również zrezygnować z całkowicie zagnieżdżonych warunkowych:

main :: IO() 
main = do a <- getArgs 
      d <- case a of 
        [x] -> return x 
        _ -> fallOverAndDie "Invalid number of arguments." 
      when (not $ dataOk d) $ fallOverAndDie "Could not read input data." 
      let r = processData d 
      when (not $ resultOk r) $ fallOverAndDie "Processing failed." 

fallOverAndDie Stosując tę ​​samą jak poprzednio. Jest to znacznie bardziej bezpośrednie tłumaczenie oryginalnej Javy.

W przypadku ogólnym, instancja Monad dla Either pozwala napisać coś bardzo podobnego do powyższego przykładu w czystym kodzie. Zaczynając od tego:

fallOverAndDie :: String -> Either String a 
fallOverAndDie = Left 

notMain x = do a <- getArgsSomehow x 
       d <- case a of 
         -- etc. etc. 

... pozostała część kodu pozostaje niezmieniona w drugim przykładzie.Możesz oczywiście użyć czegoś innego niż tylko String; aby wierniej odtworzyć wersję IO, możesz zamiast tego użyć Either (String, ExitCode).

Dodatkowo, zastosowanie Either nie ogranicza się do obsługi błędów - jeśli masz jakieś skomplikowane obliczenia przekazujących Double, korzystając Either Double Double i ten sam styl jednowartościowy jak wyżej, można użyć Left wyskoczyć wcześnie o wartości zwracanej , a następnie zawiń funkcję przy użyciu czegoś takiego jak either id id, aby zwinąć dwa wyniki i uzyskać pojedyncze Double.

3

Jednym ze sposobów jest użycie transformatora monadowego ErrorT. Dzięki niemu możesz traktować go jak zwykłą monadę, zwracać, wiązać, wszystkie dobre rzeczy, ale dostaniesz także tę funkcję, throwError. Powoduje to pominięcie następujących obliczeń aż do momentu zakończenia monadycznego obliczenia lub wywołania catchError. Jest to jednak możliwe w przypadku obsługi błędów, nie ma na celu arbitralnego opuszczania funkcji w Haskell. Zasugerowałem to, ponieważ wygląda na to, że właśnie to robisz.

Szybki przykład:

import Control.Monad.Error 
import System.Environment 

data IOErr = InvalidArgs String | GenErr String deriving (Show) 
instance Error IOErr where 
    strMsg = GenErr --Called when fail is called 
    noMsg = GenErr "Error!" 
type IOThrowsError = ErrorT IOErr IO 

process :: IOThrowsError [String] 
process = do 
    a <- liftIO getArgs 
    if length a == 0 
    then throwError $ InvalidArgs "Expected Arguments, received none" 
    else return a 

main = do 
    result <- runErrorT errableCode 
    case result of 
    Right a -> putStrLn $ show a 
    Left e -> putStrLn $ show e 
    where errableCode = do 
    a <- process 
    useArgs a 

teraz jeśli proces wyrzucił błąd, useArgs nie byłyby wykonywane.

0

To co mam wpadł

data ExtendedMaybe a = Just a | GenErr String 

isWrongArgs :: [string] -> ExtendedMaybe [string] 
isWrongArgs p = if (length p == 0) 
then GenErr "Invalid number of arguments" 
else p 

getData :: ExtendedMaybe [string] -> ExtendedMaybe sometype 
getData GenErr = GenErr 
getData [string] = if anything wrong return GenErr "could not read input data" 

processdata :: ExtendedMaybe sometype -> ExtendedMaybe sometype 
processdata GenErr = GenErr 

main = do 
    a <- getArgs 
    d <- isWrongArgs a 
    r <- getData d 
    f <- processdata r 

Z grubsza chodzi o to, że masz typ danych jak Być może, tylko zamiast Nic nie masz GenErr String, które można zdefiniować w każdej funkcji, które przetwarzają dane. Jeśli typem danych wejściowych jest GenErr, po prostu zwróć to. W przeciwnym razie sprawdź błąd danych i zwróć GenErr z odpowiednim łańcuchem. To może nie być doskonały sposób, ale wciąż w jedną stronę. To nie kończy się dokładnie w punkcie błędu, ale gwarantuje, że niewiele się dzieje po wystąpieniu błędu.

+8

Twój typ "ExtendedMaybe" jest w zasadzie taki sam, jak "Albo String" – Lee