Learn You a Haskell for Great Good! - Miran Lipovaca [87]
putStrLn "Which number in the range from 1 to 10 am I thinking of? "
numberString <- getLine
when (not $ null numberString) $ do
let number = read numberString
if randNumber == number
then putStrLn "You are correct!"
else putStrLn $ "Sorry, it was " ++ show randNumber
askForNumber newGen
We make a function askForNumber, which takes a random number generator and returns an I/O action that will prompt you for a number, and then tell you if you guessed it right.
In askForNumber, we first generate a random number and a new generator based on the generator that we got as a parameter and call them randNumber and newGen. (For this example, let’s say that the number generated was 7.) Then we tell the user to guess which number we’re thinking of. We perform getLine and bind its result to numberString. When the user enters 7, numberString becomes "7". Next, we use when to check if the string the user entered is an empty string. If it isn’t, the action consisting of the do block that is passed to when is performed. We use read on numberString to convert it to a number, so number is now 7.
Note
If the user enters some input that read can’t parse (like "haha"), our program will crash with an ugly error message. If you don’t want your program to crash on erronous input, use reads, which returns an empty list when it fails to read a string. When it succeeds, it returns a singleton list with a tuple that has your desired value as one component and a string with what it didn’t consume as the other. Try it!
We check if the number that we entered is equal to the one generated randomly and give the user the appropriate message. Then we perform askForNumber recursively, but this time with the new generator that we got. This gives us an I/O action that’s just like the one we performed, except that it depends on a different generator.
main consists of just getting a random generator from the system and calling askForNumber with it to get the initial action.
Here’s our program in action:
$ ./guess_the_number
Which number in the range from 1 to 10 am I thinking of?
4
Sorry, it was 3
Which number in the range from 1 to 10 am I thinking of?
10
You are correct!
Which number in the range from 1 to 10 am I thinking of?
2
Sorry, it was 4
Which number in the range from 1 to 10 am I thinking of?
5
Sorry, it was 10
Which number in the range from 1 to 10 am I thinking of?
Here’s another way to make this same program:
import System.Random
import Control.Monad(when)
main = do
gen <- getStdGen
let (randNumber, _) = randomR (1,10) gen :: (Int, StdGen)
putStrLn "Which number in the range from 1 to 10 am I thinking of? "
numberString <- getLine
when (not $ null numberString) $ do
let number = read numberString
if randNumber == number
then putStrLn "You are correct!"
else putStrLn $ "Sorry, it was " ++ show randNumber
newStdGen
main
It’s very similar to the previous version, but instead of making a function that takes a generator and then calls itself recursively with the new updated generator, we do all the work in main. After telling the user whether he was correct in his guess, we update the global generator and then call main again. Both approaches are valid, but I like the first one more since it does less stuff in main and also provides a function I can reuse easily.
Bytestrings
Lists are certainly useful. So far, we’ve used them pretty much everywhere. There are a multitude of functions that operate on them, and Haskell’s laziness allows us to exchange the for and while loops of other languages for filtering and mapping over lists. Since evaluation will happen only when it really needs to, things like infinite lists (and even infinite lists of infinite lists!) are no problem for us. That’s why lists can also be used to represent streams, either when reading from the standard input or when reading from files. We can just open a file and read it as a string, even though it will be accessed only when the need arises.
However, processing files as strings has one