Guessing Game
Let's examine how we might implement a quaint guess-a-number game in Haskell. The computer picks a number at random in [0, 100]. Then the user makes a guess, and the computer says whether the guess is too low, too high, or just right. If the guess isn't right, the user keeps trying, and the computer keeps giving feedback.
The first task is to generate a random number. There's no random number generator built in to the Haskell standard library. We could write our own with linear congruence, but designing one that has a uniform distribution is challenging. A better option is to use the System.Random
module, which can be installed with this single shell command:
cabal install --lib random
The cabal
package manager was likely installed with the rest of the Haskell platform.
The randomRIO
function from this module generates a random number in a range and effectively has this type signature:
randomRIO :: (a, a) -> IO a
randomRIO (lo, hi) = ...
It accepts a tuple of a lower and upper bound. It returns a tainted random number within the given range.
Because randomRIO
is an impure function, we call it from a do
block and use the <-
operator to unpack the random number from its IO
box. Additionally, we indicate what type of number we want with a type annotation. This main
generates and prints a random integer in the range [0, 100]:
import System.Random
main = do
target <- randomRIO (0, 100) :: IO Int
print target
The input-output interaction needs to happen repeatedly, so we factor it out into a recursive helper function that accepts the target number:
playRound :: Int -> IO ()
playRound target = do
-- prompt the user
-- get guess
-- if guess is target
-- congrats
-- else
-- give feedback
-- recurse
The comments are translated more or less directly into Haskell. The putStr
function prompts. The getInt
function we wrote earlier gets the user input. The feedback requires a conditional expression of some form. This implementation uses two if-else expressions:
import System.Random
import Text.Read
getInt :: IO Int
getInt = do
intString <- getLine
let intMaybe = readMaybe intString :: Maybe Int
case intMaybe of
Just int -> return int
Nothing -> do
putStr "That wasn't an integer. Try again: "
getInt
playRound :: Int -> IO ()
playRound target = do
putStr "Guess a number: "
guess <- getInt
if guess == target then
putStrLn ("Correct! The number is " ++ show target ++ ".")
else if guess < target then do
putStrLn (show guess ++ " is too low.\n")
playRound target
else do
putStrLn (show guess ++ " is too high.\n")
playRound target
main = do
target <- randomRIO (0, 100) :: IO Int
playRound target
Whenever a then or else branch has multiple statements, they are sequenced together in a do
block.