How to track the number of guesses in a simple guessing game (Haskell)

I have been trying to learn Haskell myself over the past few weeks. I am currently trying to implement a dumb game in which the computer selects a random number and the user tries to guess it. If the user is mistaken, the program informs the user that the answer is higher or lower and allows the user to guess until they guess correctly. It works for me, but I would like to add the ability to track the number of guesses that the user makes each game, and report this number to them as soon as they guess correctly.

Based on an imperative background, it would be natural to have a counter that increments every time the user makes an assumption, but you cannot do it in Haskell (at least it looks like statelessness and the immutability of all this will prevent).

I played with the idea of โ€‹โ€‹making the getGuess and giveHints functions take an additional parameter that will show the number of guesses (let's call it numGuesses), and pass (numGuesses + 1) with each call to these methods. But I could not get this to work (not to mention the fact that I did not even know if it would work).

My code is below. Any suggestions would be really appreciated. I'm mostly looking for ideas, but feel free to post the actual code too. Also feel free to let me know if my code sucks and how I could improve it if you notice anything disgusting (I only programmed for a couple of weeks!)

import System.Random import System.IO import Control.Monad main = do gen <- getStdGen let (ans,_) = randomR (1,100) gen :: (Int,StdGen) putStrLn $ "I'm thinking of a number between 1 and 100..." getGuess ans putStrLn "You guessed it in __ guesses!" putStr "Play again? " hFlush stdout desire <- getLine when ((desire !! 0) `elem` ['y','Y']) $ do putStrLn "" newStdGen main getGuess ans = do putStr "Your guess? " hFlush stdout guessStr <- getLine giveHints ans (read guessStr) giveHints ans guess = do when (ans /= guess) $ do if ans > guess then putStrLn "It higher." else putStrLn "It lower." getGuess ans 

Note. I use hflush stdout because I use line buffering, and without it, the order of some interactions is not what you would expect.

+7
source share
2 answers

In fact, you can use the counting method you were thinking about, but you still have to explicitly pass the state. But in this case, this is not a big problem. In fact, this is a pattern that is quite common for helper functions, where the actual use of the State monad would be redundant.

The pattern I'm talking about often looks like this:

 doStuff xs' = go xs' 0 where go (x:xs) n = .. etc .. 

Here is the code.

 import System.Random (randomRIO) import Control.Applicative ((<$>)) import Control.Monad (when) import Text.Printf (printf) playGame :: Int -> Int -> IO () playGame answer curGuesses = do putStrLn "What is your guess?" putStr ">" guess <- getGuessFromUser when (guess /= answer) $ do giveHints answer guess playGame answer (curGuesses + 1) when (guess == answer) $ do putStrLn "You guessed it!" printf "You guessed %d times!\n" (curGuesses + 1) giveHints :: Int -> Int -> IO () giveHints answer guess | answer > guess = putStrLn "It higher!" | otherwise = putStrLn "It lower!" getGuessFromUser :: IO Int getGuessFromUser = do read <$> getLine main :: IO () main = do answer <- randomRIO (1, 100) putStrLn "I'm thinking of a number between 1 and 100." playGame answer 0 

Notes

  • <$> fmap
  • I used randomRIO , as Daniel mentioned, since we are already in the IO monad.
  • I did not need to use hSetBuffering or hFlush using the command line on Windows to get the correct output. YMMV, however.
+8
source

Adding an extra parameter to the number of guesses is exactly how you make this function functionally.

The main functional way of thinking is that if you have a function that should behave differently depending on the different values โ€‹โ€‹of "something", then this something is a parameter of the function. This is a simple consequence of purity; a function should always return the same for the same inputs.

When you move on to more advanced methods, there are various ways to โ€œhideโ€ additional parameters in order to free you from writing / passing them explicitly; this is basically what the State monad does, and one way to think of the IO monad is to do something like that. But while you're new to functional programming, it's probably more useful to get used to this way of thinking; you pass information to the function that you call through your arguments, and get the information back through your arguments. You cannot resort to the imperative trick simply by leaving information in some external place (for example, the value of the counter), where you know that the function you are calling will look for it (or even modify it).

+3
source

All Articles