8
votes

Devinez mon numéro, un mal de tête monadique

Pour tester mes compétences à Haskell, j'ai décidé que je voulais mettre en œuvre le tout premier jeu que vous trouverez dans terres de Lisp em> / royaume de raquette em>. Le "devine mon numéro" em> jeu. Le jeu s'appuie sur l'état mutable à exécuter, car il doit constamment mettre à jour les limites supérieure et inférieure du programme à domicile sur la valeur de la valeur de l'utilisateur.

Cela va un peu comme ceci: P> XXX PRE>

Maintenant, ce type de chose (à ma connaissance) n'est pas entièrement possible dans HASKELLL, appelant une fonction de la part de la réplique qui modifie l'état mutable global, puis imprime le résultat immédiatement après, comme il enfreint le principe de l'immutabilité. Par conséquent, toute l'interaction doit vivre à l'intérieur d'un io code> et / ou état code> monad. Et c'est là que je suis coincé. p>

Je n'arrive pas à pouvoir envelopper mon esprit autour de la combinaison du io code> monad et de l'état code> monade, donc je peux obtenir une entrée, imprimer Résultats et modifier l'état, tous dans la même fonction. P>

Voici ce que j'ai jusqu'à présent: p>

type Bound = (Int, Int) -- left is the lower bound, right is the upper

initial :: Bound
initial = (1, 100)

guess :: Bound -> Int
guess (l, u) = (l + u) `div` 2

smaller :: State Bound ()
smaller = do
  bd@(l, _) <- get
  let newUpper = max l $ pred $ guess bd
  put $ (l, newUpper)

bigger :: State Bound ()
bigger = do
  bd@(_, u) <- get
  let newLower = min u $ succ $ guess bd
  put $ (newLower, u)
  • Imprimer Initial Guess Li>
  • Commande de réception vouloir un nombre plus petit / plus grand li>
  • Modifier l'état en conséquence li>
  • appelez la fonction de manière récursive afin qu'elle devine encore li> ul>

    Comment puis-je combiner io code> et état code> d'une manière élégante pour y parvenir? p>

    Remarque: je suis conscient que Cela peut probablement être atteint sans l'utilisation de l'état du tout; Mais je veux que cela reste fidèle à l'original p> p>


3 commentaires

Attendez, c'est donc l'ordinateur qui devine le nombre?


@Jefffrey Oui, dans la version LISP, l'ordinateur devine, pas la personne; C'est pourquoi il est tellement plus amusant de mettre en œuvre


Voir aussi Gestion de l'état .


4 Réponses :


1
votes

Voici une solution à l'aide du transformateur statut . Points notables:

  1. Il lit l'entrée de l'utilisateur à l'aide de getline au lieu d'utiliser le repl.
  2. Il lit beaucoup comme un programme impératif, sauf que vous devez ajouter liftidio à n'importe quelle action IO.
  3. Vous exécutez la boucle avec runstatet où vous fournissez également l'état initial.

    Le programme: xxx


1 commentaires

Voulez-vous dire que vous voulez interagir avec l'utilisateur via la RÉPP? Vous ne pouvez pas utiliser votre code existant car une signature comme lié ​​à l'état () ne permet aucun IO d'être effectué et que vous ne pouvez pas exécuter plus grand et plus petit à partir du repliant - ça n'a aucun sens. Vous pouvez appeler Runstate Bigger à partir de la RÉPL, mais vous devez également fournir l'état.



2
votes

Vous n'avez pas à utiliser l'état monade du tout dans cet exemple. Voici un exemple qui transmet l'état sous forme de paramètre: xxx

sinon, le concept que vous recherchez est monad transformers . Par exemple, utilisation du statut: xxx

voir ceci chapitre du monde réel Haskell pour un didacticiel sur le sujet des transformateurs de Monad.


4 commentaires

J'aimerais pouvoir utiliser mon code existant si possible


@ElectricCoffee: Et si votre code existant est faux?


Une bonne programmation consiste à la séparation des préoccupations, ayant une fonction qui fait que tout ce que les fonctions réutilisables plus petites est généralement toujours meilleure. Ce que j'ai à l'heure actuelle se comporte la façon dont je m'attends, ce sont les bits restants qui doivent tomber en place


@ElectricCoffee J'ai modifié l'exemple du transformateur pour utiliser des adaptations de votre plus petit plus grand fonctions. J'espère que vous voyez comment cela compose. L'utilisation de votre code d'origine n'est pas possible.



12
votes

Vous pouvez combiner différentes monades à l'aide de MONAD Transformers - dans ce cas statut code>. Vous pouvez utiliser votre code existant en modifiant les signatures de type pour utiliser statut code>: xxx pré>

alors vous pouvez écrire une fonction pour exécuter le jeu donné un paramètre d'état: p> xxx pré>

Vous utilisez lifidio code> pour soulever un IO code> Action dans le statuète liée IO code> monad, permettant Vous inviter à entrer et lire la ligne suivante. P>

Enfin, vous pouvez exécuter le jeu en utilisant runstatet code>: p>

runStateT game initial


2 commentaires

Très beau, très simple, très élégant! C'est exactement ce que j'avais à l'esprit


ne serait-il pas préférable d'écrire impression $ $ S S au lieu de postStrln (show. suppose $ s) ?, les appels impression " code> montrer interne quand même



7
votes

Ce que vous demandez est en quelque sorte possible ...

$ ghci guess.hs 
GHCi, version 7.9.20141202: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( guess.hs, interpreted )
Ok, modules loaded: Main.
*Main> (guess, smaller, bigger) <- makeGame 
*Main> guess
50
*Main> smaller
25
*Main> bigger
37
*Main> 


0 commentaires