Domanda

Sto imparando Haskell nella speranza che mi aiuti ad avvicinarmi alla programmazione funzionale. In precedenza, ho usato principalmente linguaggi con sintassi simile a C, come C, Java e D.

Ho una piccola domanda sullo stile di codifica di un blocco di controllo if / else utilizzato dal tutorial su Wikibooks . Il codice è simile al seguente:

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   if (read guess) < num
     then do putStrLn "Too low!"
             doGuessing num
     else if (read guess) > num
            then do putStrLn "Too high!"
                    doGuessing num
            else do putStrLn "You Win!"

Mi confonde, perché questo stile di codifica viola totalmente lo stile raccomandato in linguaggi simili a C, dove dovremmo rientrare se , altrimenti se e altro nella stessa colonna.

So che non funziona in Haskell, perché sarebbe un errore di analisi se indentassi else nella stessa colonna di if .

E il seguente stile? Penso che sia molto più chiaro di quello sopra. Ma poiché quanto sopra è usato da Wikibooks e Yet Another Haskell Tutorial, che è contrassegnato come "miglior tutorial disponibile online" sul sito Web ufficiale di Haskell, non sono sicuro che questo stile di codifica sia una convenzione nei programmi Haskell.

doGuessing num = do
    putStrLn "Enter your guess:"
    guess <- getLine
    if (read guess) < num then
        do 
            putStrLn "Too low!"
            doGuessing num
        else if (read guess) > num then do 
            putStrLn "Too high!"
            doGuessing num
        else do 
            putStrLn "You Win!"

Quindi, sono curioso di sapere quale stile di codifica viene usato più spesso o c'è un altro stile di codifica per questo pezzo di codice?

È stato utile?

Soluzione

Lo stile Haskell è funzionale, non imperativo! Invece di " fallo allora che " pensa a combinare le funzioni e descrivere cosa farà il tuo programma, non come.

Nel gioco, il tuo programma chiede all'utente un'ipotesi. Un'ipotesi corretta è un vincitore. Altrimenti, l'utente riprova. Il gioco continua fino a quando l'utente non indovina correttamente, quindi scriviamo che:

main = untilM (isCorrect 42) (read `liftM` getLine)

Questo utilizza un combinatore che esegue ripetutamente un'azione ( getLine tira una riga di input e read converte quella stringa in un numero intero in questo caso) e ne controlla il risultato:

untilM :: Monad m => (a -> m Bool) -> m a -> m ()
untilM p a = do
  x <- a
  done <- p x
  if done
    then return ()
    else untilM p a

Il predicato (applicato parzialmente in main ) verifica l'ipotesi rispetto al valore corretto e risponde di conseguenza:

isCorrect :: Int -> Int -> IO Bool
isCorrect num guess =
  case compare num guess of
    EQ -> putStrLn "You Win!"  >> return True
    LT -> putStrLn "Too high!" >> return False
    GT -> putStrLn "Too low!"  >> return False

L'azione da eseguire fino a quando il giocatore indovina correttamente è

read `liftM` getLine

Perché non mantenerlo semplice e comporre semplicemente le due funzioni?

*Main> :type read . getLine

<interactive>:1:7:
    Couldn't match expected type `a -> String'
           against inferred type `IO String'
    In the second argument of `(.)', namely `getLine'
    In the expression: read . getLine

Il tipo di getLine è IO String , ma read vuole un String puro.

La funzione liftM da Control.Monad assume una funzione pura e la "solleva" in una monade. Il tipo di espressione ci dice molto su ciò che fa:

*Main> :type read `liftM` getLine
read `liftM` getLine :: (Read a) => IO a

È un'azione I / O che quando eseguito ci restituisce un valore convertito con read , nel nostro caso un Int . Ricorda che readLine è un'azione I / O che produce valori String , quindi puoi pensare a liftM come a consentirci di applicare read “dentro” la monade IO

Gioco di esempio:

1
Too low!
100
Too high!
42
You Win!

Altri suggerimenti

Puoi utilizzare il " case " -construct:

doGuessing num = do
    putStrLn "Enter your guess:"
    guess <- getLine
    case (read guess) of
        g | g < num -> do 
            putStrLn "Too low!"
            doGuessing num
        g | g > num -> do 
            putStrLn "Too high!"
            doGuessing num
        otherwise -> do 
            putStrLn "You Win!"

Un piccolo miglioramento all'affermazione del caso di mattiast (modificerei, ma mi manca il karma) è l'uso della funzione di confronto, che restituisce uno dei tre valori, LT, GT o EQ:

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   case (read guess) `compare` num of
     LT -> do putStrLn "Too low!"
              doGuessing num
     GT -> do putStrLn "Too high!"
              doGuessing num
     EQ -> putStrLn "You Win!"

Mi piacciono molto queste domande di Haskell e incoraggerei gli altri a postare di più. Spesso hai la sensazione che ci sia preso per essere un modo migliore per esprimere ciò che stai pensando, ma Haskell inizialmente è così estraneo che non mi verrà in mente nulla.

Domanda bonus per il giornalista Haskell: qual è il tipo di doGuessing?

Il modo in cui Haskell interpreta se ... allora ... altro all'interno di un blocco do è molto in linea con l'intera sintassi di Haskell.

Ma molte persone preferiscono una sintassi leggermente diversa, permettendo a quindi e else di apparire allo stesso livello di rientro del corrispondente se . Pertanto, GHC viene fornito con un'estensione del linguaggio opt-in denominata DoAndIfThenElse , che consente questa sintassi.

L'estensione DoAndIfThenElse è diventata parte del linguaggio principale nell'ultima revisione della specifica Haskell, Haskell 2010 .

Nota che il fatto che devi indentare il 'then' e 'else' all'interno di un blocco 'do' è considerato da molti un bug. Probabilmente verrà risolto in Haskell '(Haskell prime), la prossima versione delle specifiche Haskell.

Puoi anche usare il raggruppamento esplicito con parentesi graffe. Vedi la sezione layout di http://www.haskell.org/tutorial/patterns.html

Non lo consiglierei però. Non ho mai visto nessuno usare il raggruppamento esplicito oltre ad alcuni casi speciali. Di solito guardo il Codice preludio standard per esempi di stile.

Uso uno stile di codifica come il tuo esempio da Wikibooks. Certo, non segue le linee guida C, ma Haskell non è C, ed è abbastanza leggibile, soprattutto una volta che ti ci abitui. È inoltre modellato sullo stile degli algoritmi utilizzati in molti libri di testo, come Cormen.

Vedrai diversi stili di rientro per Haskell. Molti di essi sono molto difficili da mantenere senza un editor impostato per rientrare esattamente in qualunque stile.

Lo stile che visualizzi è molto più semplice e meno impegnativo per l'editor, e penso che dovresti rispettarlo. L'unica incoerenza che posso vedere è che metti il ??primo do sulla sua linea mentre metti gli altri dos dopo allora / else.

Seguire gli altri consigli su come pensare al codice in Haskell, ma attenersi al proprio stile di rientro.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top