How Can I Fix a Parse Error in My Haskell Number Guessing Game?

When I first started writing my number guessing game in Haskell, I had a clear goal: let users pick a difficulty, guess a number, and count how many tries it took them to win. Sounds simple enough Well, it didn’t go as smoothly as I thought I’ll walk you through what went wrong, how I fixed it, and what I learned.

What I Want to Build

The concept was straightforward:

  • Present a menu to the user with three difficulty levels.
  • Generate a random number based on the difficulty.
  • Let the user guess repeatedly until they get it right.
  • Count how many guesses it takes and display that at the end.

The Syntax Error That Stopped Everything

Here’s the first version of my code that tripped me up:

choice <- readLn
| choice == 1 = "You selected easy" >> easy
| choice == 2 = "You selected medium" >> medium
| choice == 3 = "You selected hard" >> hard
| otherwise = "You did not selected a valid choice" >> main

At first glance, it seemed logical. I was trying to pattern match on the value of choice using guards (|). But Haskell didn’t like it at all.

The Error

Parse error in pattern: putStrLn
Possibly caused by a missing 'do'?

That message pointed me in the wrong direction at first. But the real issue was this:

You cannot use guards (|) outside of a function definition. They’re only valid inside things like case, if, or when defining a function with multiple conditions.

Use case Expression

So I rewrote the logic using a case statement, which is perfectly suited for handling multiple choices. That solved the error and made the code much cleaner.

Adding a Guess Counter

Once the syntax was fixed, I wanted to add a new feature: count how many guesses the user makes.

The approach was to pass a counter variable (count) into the game loop and increment it with each incorrect guess. This way, when the user guesses correctly, we can display how many tries it took.

Final Working Code

Here’s the final version of the game with everything put together:

import System.Random (randomRIO)

main :: IO ()
main = do
putStrLn "Do you want to play:"
putStrLn "1) Easy\n2) Medium\n3) Hard"
putStrLn "Select your difficulty by entering a number (1, 2 or 3):"
choice <- readLn :: IO Int
case choice of
1 -> putStrLn "You selected easy" >> easy
2 -> putStrLn "You selected medium" >> medium
3 -> putStrLn "You selected hard" >> hard
_ -> putStrLn "Invalid choice, try again." >> main

easy :: IO ()
easy = do
putStrLn "Guess a number between 1 and 13:"
rNumber <- randomRIO (1, 13)
loop rNumber 1

medium :: IO ()
medium = do
putStrLn "Guess a number between 1 and 25:"
rNumber <- randomRIO (1, 25)
loop rNumber 1

hard :: IO ()
hard = do
putStrLn "Guess a number between 1 and 50:"
rNumber <- randomRIO (1, 50)
loop rNumber 1

loop :: Int -> Int -> IO ()
loop rNumber count = do
putStr "Your guess: "
userInput <- readLn
case compare userInput rNumber of
EQ -> putStrLn (" Correct! It took you " ++ show count ++ " guesses.") >> main
LT -> putStrLn "Too low! Try again." >> loop rNumber (count + 1)
GT -> putStrLn "Too high! Try again." >> loop rNumber (count + 1)

What I Learn

  • Guards only work in function definitions, not inside do blocks.
  • case expressions are perfect for branching based on user input.
  • Haskell’s type system and error messages may seem cryptic at first, but they guide you toward good patterns.
  • Adding state (like a guess counter) is easy in Haskell by passing parameters recursively.

Final Thought

This project reminded me that even simple games can teach you powerful lessons. Whether you’re new to Haskell or just exploring functional programming, small projects like this are the best way to get comfortable with pattern matching, recursion, and IO.

Related blog posts