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?

War es hilfreich?

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top