Guter Haskell-Codierungsstil des if/else-Kontrollblocks?
-
02-07-2019 - |
Frage
Ich lerne Haskell in der Hoffnung, dass es mir hilft, der funktionalen Programmierung näher zu kommen.Bisher habe ich hauptsächlich Sprachen mit C-ähnlicher Syntax verwendet, wie C, Java und D.
Ich habe eine kleine Frage zum Codierungsstil eines if
/else
Steuerblock, der von verwendet wird Tutorial zu Wikibooks.Der Code sieht wie folgt aus:
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!"
Es verwirrt mich, weil dieser Codierungsstil völlig gegen den empfohlenen Stil in C-ähnlichen Sprachen verstößt, wo wir einrücken sollten if
, else if
, Und else
in derselben Spalte.
Ich weiß, dass es in Haskell einfach nicht funktioniert, weil es einen Analysefehler bedeuten würde, wenn ich einrücken würde else
in der gleichen Spalte wie if
.
Aber was ist mit dem folgenden Stil?Ich denke, es ist viel klarer als das obige.Da das Obige jedoch von Wikibooks und Yet Another Haskell Tutorial verwendet wird, das auf der offiziellen Haskell-Website als „bestes online verfügbares Tutorial“ ausgezeichnet wird, bin ich mir nicht sicher, ob dieser Codierungsstil in Haskell-Programmen üblich ist.
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!"
Ich bin also neugierig, welcher Codierungsstil häufiger verwendet wird – oder gibt es einen anderen Codierungsstil für diesen Codeabschnitt?
Lösung
Haskell Stil ist funktional, nicht zwingend notwendig! Anstatt „dies tut, dann, dass“ denkt über Funktionen kombinieren und zu beschreiben was Ihr Programm tun, nicht wie.
Im Spiel Ihr Programm fragt den Benutzer für eine Vermutung. Eine richtige Vermutung ist ein Gewinner. Andernfalls versucht der Benutzer erneut. Das Spiel geht weiter, bis der Benutzer richtig geraten, so dass wir schreiben, dass:
main = untilM (isCorrect 42) (read `liftM` getLine)
Dieses verwendet einen Kombinator, der wiederholt eine Aktion ausgeführt wird (getLine
zieht eine Linie von Ein- und read
wandelt diese Zeichenfolge in eine ganze Zahl in diesem Fall), und überprüft dessen Ergebnis:
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
Das Prädikat (teilweise in main
angewandt) prüft die Vermutung gegen den richtigen Wert und reagiert entsprechend:
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
Die Aktion, die ausgeführt werden, bis der Spieler richtig geraten ist
read `liftM` getLine
Warum nicht halten Sie es einfach und nur die beiden Funktionen zusammenstellen?
*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
Die Art der getLine
ist IO String
, aber read
will ein reines String
.
Die Funktion liftM
von Control.Monad nimmt eine reine Funktion und „Aufzüge“ in eine Monade. Die Art des Ausdrucks erzählt uns viel über das, was sie tut:
*Main> :type read `liftM` getLine read `liftM` getLine :: (Read a) => IO a
Es ist ein I / O-Aktion, die bei der Ausführung gibt uns einen Wert mit read
, eine Int
in unserem Fall zurückgewandelt. Daran erinnern, dass readLine
ist ein I / O-Aktion, die String
Werte ergibt, so kann man sich vorstellen liftM
wie es uns ermöglicht, read
„innerhalb“ des IO
Monade anzuwenden.
Beispiel-Spiel:
1 Too low! 100 Too high! 42 You Win!
Andere Tipps
Sie können den "Fall" -Konstrukt verwenden:
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!"
Eine geringfügige Verbesserung mattiast Fall Aussage (I bearbeiten würde, aber mir fehlt das Karma) ist die Vergleichsfunktion zu verwenden, die eine der drei Werte zurückgibt, LT, GT oder 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!"
Ich mag diese Haskell Fragen, und ich würde andere ermutigen, mehr zu schreiben. Oft fühlt man sich wie es got zu sein, ein besserer Weg, um auszudrücken, was du denkst, aber Haskell ist zunächst so fremd, dass nichts in dem Sinne kommen werden.
Bonusfrage für die Haskell journyman: Was die Art des doGuessing ist
Die Art und Weise Haskell if ... then ... else
innerhalb eines do
Block interpretiert ist sehr viel mit dem ganzen Haskell Syntax zu halten.
Aber viele Menschen es vorziehen, eine etwas andere Syntax, ermöglicht then
und else
auf der gleichen Einrückungsebene wie der entsprechenden if
erscheinen. Daher GHC kommt mit einer Opt-in-Sprache-Erweiterung namens DoAndIfThenElse
, die diese Syntax erlaubt.
Die DoAndIfThenElse
Erweiterung ist in der aktuellen Überarbeitung der Haskell-Spezifikation, Haskell 2010 .
Beachten Sie, dass die Tatsache, dass Sie die ‚dann‘ einrücken und ‚else‘ innerhalb eines ‚do‘ Block einen Fehler von vielen betrachtet wird. Es wird wahrscheinlich in Haskell‘(Haskell Prime), die nächsten Version der Haskell-Spezifikation festgelegt werden.
Sie können auch explizite Gruppierung mit geschweiften Klammern verwenden. Siehe den Layoutbereich von http://www.haskell.org/tutorial/patterns.html
Ich würde es nicht empfehlen, dass, obwohl. Ich habe noch nie jemand außer in einigen Sonderfällen verwendet explizite Gruppierung gesehen. Ich sehe in der Regel auf der Standard-Prelude Code Beispiele für Stil.
Ich verwende eine Codierung Stil wie Ihr Beispiel von Wikibooks. Sicher, es folgt nicht den C-Richtlinien, aber Haskell ist nicht C, und es ist ziemlich lesbar, vor allem wenn Sie sich daran gewöhnen. Es ist auch nach der Art von Algorithmen in vielen Lehrbüchern, wie Cormen verwendet gemustert.
Sie werden eine Reihe verschiedener Einrückungsstile für Haskell sehen.Die meisten von ihnen sind ohne einen Editor, der so eingerichtet ist, dass er genau in jedem Stil einrückt, sehr schwer zu warten.
Der von Ihnen angezeigte Stil ist viel einfacher und stellt weniger Anforderungen an den Redakteur, und ich denke, Sie sollten dabei bleiben.Die einzige Inkonsistenz, die ich erkennen kann, besteht darin, dass Sie das erste do in einer eigenen Zeile platzieren, während Sie die anderen dos nach dem then/else einfügen.
Beachten Sie die anderen Ratschläge zum Denken über Code in Haskell, aber bleiben Sie bei Ihrem Einrückungsstil.