Question

J'apprends Haskell dans l'espoir que cela m'aidera à me rapprocher de la programmation fonctionnelle. Auparavant, j'utilisais principalement des langages utilisant une syntaxe de type C, comme C, Java et D.

J'ai une petite question sur le style de codage d'un bloc de contrôle si / else utilisé par le tutoriel sur Wikibooks . Le code ressemble à ceci:

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

Cela me laisse perplexe, car ce style de codage viole totalement le style recommandé dans les langages de type C, où nous devrions indenter si , sinon si et . sinon dans la même colonne.

Je sais que cela ne fonctionne tout simplement pas dans Haskell, car ce serait une erreur d'analyse si j'indenterais else dans la même colonne que si .

Mais qu'en est-il du style suivant? Je pense que c'est beaucoup plus clair que celui ci-dessus. Mais puisque ce qui précède est utilisé par Wikibooks et Yet Another Haskell, le didacticiel qui porte la mention "meilleur didacticiel disponible en ligne". sur le site Web officiel de Haskell, je ne sais pas si ce style de codage est une convention dans les programmes 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!"

Alors, je suis curieux de savoir quel style de codage est utilisé plus souvent - ou existe-t-il un autre style de codage pour ce morceau de code?

Était-ce utile?

La solution

Le style Haskell est fonctionnel, pas impératif! Plutôt que de "faire ceci, alors", Pensez à combiner des fonctions et à décrire ce que votre programme fera, pas comment.

Dans le jeu, votre programme demande à l'utilisateur de deviner. Une supposition correcte est un gagnant. Sinon, l'utilisateur essaie à nouveau. Le jeu continue jusqu'à ce que l'utilisateur devine correctement. Nous écrivons donc ceci:

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

Ceci utilise un combinateur qui exécute plusieurs fois une action ( getLine extrait une ligne d'entrée et read convertit cette chaîne en un entier dans ce cas) et vérifie son résultat:

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

Le prédicat (partiellement appliqué dans main ) compare la conjecture à la valeur correcte et répond en conséquence:

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'action à exécuter jusqu'à ce que le joueur devine correctement est

read `liftM` getLine

Pourquoi ne pas rester simple et composer les deux fonctions?

*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

Le type de getLine est Chaîne d'E / S , mais lu veut une Chaîne pure.

La fonction liftM de Control.Monad prend une fonction pure et & # 8220; lève & # 8221; # 8221; dans une monade. Le type de l'expression nous en dit long sur ce qu'il fait:

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

Il s’agit d’une action d’entrée / sortie qui, une fois exécutée, nous renvoie une valeur convertie avec read , un Int dans notre cas. Rappelez-vous que readLine est une action d'E / S qui génère des valeurs String , de sorte que vous pouvez penser à liftM comme nous permettant d'appliquer en lecture & # 8220; à l'intérieur & # 8221; la monade IO .

Exemple de jeu:

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

Autres conseils

Vous pouvez utiliser le "cas" -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!"

Une amélioration mineure de l'instruction case de mattiast (je l'éditerais, mais le karma me manque) consiste à utiliser la fonction de comparaison, qui renvoie l'une des trois valeurs, LT, GT ou 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!"

J'aime beaucoup ces questions sur Haskell et j'encourage les autres à en publier davantage. Souvent, vous avez l’impression qu’il a obtenu d’être un meilleur moyen d’exprimer ce que vous pensez, mais Haskell est initialement si étranger que rien ne me vient à l’esprit.

Question de prime pour le journaliste de Haskell: quel est le type de travail à suivre?

La façon dont Haskell interprète si ... then ... else dans un bloc do correspond très bien à la syntaxe entière de Haskell.

Mais beaucoup de gens préfèrent une syntaxe légèrement différente, permettant à then et à else d'apparaître au même niveau d'indentation que le if correspondant . Par conséquent, GHC est fourni avec une extension de langage opt-in appelée DoAndIfThenElse , qui autorise cette syntaxe.

L'extension DoAndIfThenElse est intégrée au langage principal dans la dernière révision de la spécification Haskell, Haskell 2010 .

Notez que le fait de mettre en retrait le "alors" et le "sinon" à l'intérieur d'un bloc "do" est considéré par beaucoup comme un bogue. Il sera probablement corrigé dans Haskell '(Haskell prime), la prochaine version de la spécification Haskell.

Vous pouvez également utiliser le regroupement explicite avec des accolades. Voir la section sur la disposition de http://www.haskell.org/tutorial/patterns.html

Je ne recommanderais pas cela cependant. Je n'ai jamais vu personne utiliser un groupement explicite à part quelques cas particuliers. Je regarde habituellement le code Prelude standard pour des exemples de style.

J'utilise un style de codage comme votre exemple de Wikibooks. Bien sûr, il ne suit pas les directives C, mais Haskell n’est pas C, et il est assez lisible, en particulier une fois que vous vous y êtes habitué. Il est également inspiré du style des algorithmes utilisés dans de nombreux manuels, comme Cormen.

Vous verrez un tas de styles d’indentation différents pour Haskell. La plupart d’entre eux sont très difficiles à maintenir sans un éditeur configuré pour indenter exactement quel que soit le style.

Le style que vous affichez est beaucoup plus simple et moins exigeant pour l'éditeur, et je pense que vous devriez vous en tenir à cela. La seule incohérence que je puisse constater est que vous mettez la première action sur sa propre ligne, tandis que vous placez les autres actions après l'option / else.

Suivez les autres conseils sur la manière de réfléchir au code dans Haskell, mais respectez votre style d'indentation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top