2012-12-20 18 views
9

Sto eseguendo il porting di un'applicazione Java su Haskell. Il metodo principale dell'applicazione Java segue il modello:Come implementare l'uscita anticipata/il ritorno in 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); 
    } 

    ... 
} 

Così ho diverse fasi e dopo ogni passo possibile uscire con un codice di errore, o continuare al passo successivo.

Il mio tentativo di porting questo per Haskell è la seguente:

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. 

Con questa soluzione, avrò un sacco di nidificato if-then-else (uno per ogni punto di uscita del codice Java originale).

Esiste un modo più elegante/idiomatico per implementare questo modello in Haskell? In generale, che cos'è un modo idiomatico di Haskell per implementare un exit/return anticipato come usato in un linguaggio imperativo come Java?

+1

Si prega di leggere l'esempio di Walking the Line dalla pagina http://learnyouahaskell.com/a-fistful-of-monads. Fornisce un esempio di Monad che utilizza il tipo di dati Forse. Una volta che il risultato di qualsiasi espressione è Nothing, il risultato di tutte le seguenti espressioni è Nothing, buono come sei uscito nel punto di errore. –

+0

@ManojR - Forse non è proprio adatto qui poiché vuoi anche il motivo del fallimento. – Lee

+0

L'approccio classico è dividere il processo in una funzione di elaborazione "fidata" (che presuppone che i parametri siano corretti) e una funzione di controllo di integrità "paranoico" (che controlla solo se i parametri sono corretti) ... –

risposta

6

Un approccio un po 'più ragionevole in Haskell che utilizza lo stesso tipo di logica condizionale si è tentato potrebbe essere simile a questo:

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... 

In questo caso particolare, dato che exitWith termina il programma comunque, potremmo anche meno delle condizionali nidificati interamente:

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." 

Utilizzando la stessa fallOverAndDie come prima. Questa è una traduzione molto più diretta del Java originale.

Nel caso generale, l'istanza Monad per Either consente di scrivere qualcosa di molto simile al secondo esempio di cui sopra in puro codice. A partire da questo, invece:

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

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

... il resto del codice è invariato dal mio secondo esempio.Ovviamente puoi usare anche qualcosa di diverso dal String; per ricreare più fedelmente la versione IO, è possibile utilizzare invece Either (String, ExitCode).

Inoltre, questo uso di Either non limitati ad essi la gestione degli errori - se avete qualche calcolo complicato restituire un Double, utilizzando Either Double Double e lo stesso stile monadico come sopra, è possibile utilizzare Left per salvare presto con un valore di ritorno , quindi avvolgere la funzione utilizzando qualcosa come either id id per comprimere i due risultati e ottenere un singolo Double.

3

Un modo è utilizzare il trasformatore monad ErrorT. Con esso puoi trattarlo come una normale monade, tornare, legare, tutte quelle cose buone, ma ottieni anche questa funzione, throwError. Questo ti fa saltare i seguenti calcoli fino a raggiungere la fine del calcolo monadico, o quando chiami catchError. Questo però è per la gestione degli errori, non è pensato per uscire arbitrariamente da una funzione in Haskell. L'ho suggerito perché sembra che sia quello che stai facendo.

Un esempio veloce:

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 

ora se processo ha generato un errore, non sarebbe eseguita useArgs.

0

Questo è quello che ho si avvicinò con

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 

Circa l'idea è che avere un tipo di dati come forse una, solo che invece di Nulla di quanto avete genErr stringa, che si definisce in ogni funzione i dati di processo. Se il tipo di dati di input è GenErr, restituiscilo semplicemente. Altrimenti controlla l'errore nei dati e restituisci GenErr con la stringa appropriata. Questo potrebbe non essere il modo perfetto, ma comunque a senso unico. Questo non esce nel punto esatto dell'errore, ma garantisce che non succeda molto dopo che si è verificato un errore.

+8

Il tipo 'ExtendedMaybe' è fondamentalmente uguale a' Either String' – Lee

Problemi correlati