Buon stile di codifica Haskell del blocco di controllo if / else?
-
02-07-2019 - |
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?
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
*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.