Hello! Today’s topic is “Thinking About Haskell and Problem-Solving Through the FizzBuzz Challenge.”
This isn’t a Haskell tutorial or a programming guide. It’s more of a personal essay—probably the kind of thing that belongs scribbled on the back of a flyer. Consider it something to read during a coffee break (although it may not be particularly relaxing).
Note: This is not another “I solved the FizzBuzz problem in Haskell!” article.
Programming Languages and Paradigms
When writing software, we first decide how we want to solve a problem, and then express that approach in code. You could also think of an “approach” as a strategy or a way of thinking.
Naturally, functional programming has approaches that fit the functional paradigm. In general, functional programs describe what the data should look like, rather than the sequence of steps needed to produce it. Techniques such as applying map, filter, or fold (reduce) to data structures are common examples of this style.
Imperative programming, on the other hand, focuses on describing the procedure for obtaining the result. It typically relies on mutable state, assignments, and loops to express the computation step by step.
In this article, I’d like to explore the FizzBuzz problem in Haskell from both functional and imperative perspectives.
What Is Haskell?
Haskell is a pure functional programming language with no side effects.
The term functional language is somewhat ambiguous, but in this article I’ll use it to mean a language designed for a programming paradigm that expresses relationships between values and states through functions and expressions, rather than describing the procedure for computing a result.
Because of this, developers who are accustomed to imperative programming often find Haskell unfamiliar or difficult to approach at first.
What Is the FizzBuzz Problem?
The FizzBuzz problem hardly needs an introduction, but here’s a quick recap.
Starting from 1, count upward to n. Whenever a number is divisible by 3, output “Fizz”. If it’s divisible by 5, output “Buzz”. If it’s divisible by both 3 and 5, output “FizzBuzz” instead.
In programming, we naturally print these values to standard output instead of saying them aloud.
Considering Different Approaches to FizzBuzz
The problem itself is simple: process the integers from 1 to n, changing the output depending on each number.
For this discussion, let’s assume n = 30 and consider three different approaches.
- Approach 1: Generate a list of integers from 1 to n, apply a conversion function to produce a list of output strings, and then print that list.
- Approach 2: Count from 1 to n, printing the corresponding result during each iteration.
- Approach 3: Iterate through the numbers one by one while gradually building an output list, then print the completed list.
Approach 1 is commonly associated with functional programming. We transform a list of numbers into a list of strings by applying a function.
Approach 2 represents the classic imperative mindset, using a loop such as for or while.
Approach 3 is also imperative in spirit. Imagine repeatedly appending elements to an array inside a loop.
That said, this isn’t an article arguing that Haskell should always use Approach 1 simply because it’s a functional language.
First, Let’s Express the Ideas in Python
Approach 1 looks something like this in Python. It’s probably the first solution that comes to mind for someone familiar with functional programming.
We create a list of data, apply a transformation function to it, and obtain the final result.
Python Implementation of Approach 1
# Conversion function for FizzBuzz
def conv(i):
if i % 15 == 0:
return 'FizzBuzz'
elif i % 3 == 0:
return 'Fizz'
elif i % 5 == 0:
return 'Buzz'
else:
return str(i)
# Output
list(map(print, map(conv, range(1,30))))
Approach 2 is much more familiar to programmers who come from imperative languages.
Here, we increment a counter and print the corresponding value during each iteration. Notice how the variable cnt is updated destructively. Each time the loop runs, its value changes from 1 to 2 to 3, and so on.
Python Implementation of Approach 2
cnt = 1
while cnt <= 30:
if cnt % 15 == 0:
print('FizzBuzz')
elif cnt % 3 == 0:
print('Fizz')
elif cnt % 5 == 0:
print('Buzz')
else:
print(str(cnt))
cnt += 1
Approach 3 is something of a hybrid between the first two.
Like Approach 2, it eventually produces a list of output strings, but instead of generating the list through a transformation, it gradually builds the list inside a loop by appending elements one at a time.
In Python, it looks something like this.
Python Implementation of Approach 3
li = []
for i in range(1,31):
if i % 15 == 0:
li.append('FizzBuzz')
elif i % 3 == 0:
li.append('Fizz')
elif i % 5 == 0:
li.append('Buzz')
else:
li.append(str(i))
list(map(print, li))
Expressing These Approaches in Haskell
Now we arrive at the main topic.
What would these approaches look like in Haskell?
When programming in a functional style, Approach 1—transforming one list into another with map—is probably the most common solution.
Approaches 2 and 3, on the other hand, are often dismissed before they’re even considered.
This time, however, I’d like to deliberately explore approaches that appear to rely on mutable state.
Approach 1: Transforming a List with map
Let’s begin with the most straightforward solution.
There’s nothing particularly surprising here—we simply use map (or equivalently <$> or liftM) to transform a list.
main :: IO ()
main = mapM_ putStrLn mkList
mkList :: [String]
mkList = map convert list
where
list = [1..30] :: [Int]
convert :: Int -> String
convert n
| n `mod` 15 == 0 = "FizzBuzz"
| n `mod` 3 == 0 = "Fizz"
| n `mod` 5 == 0 = "Buzz"
| otherwise = show n
Simple enough.
We generate a list of integers from 1 to 30, transform it into a list of strings using map, and then print each element by applying the putStrLn IO action with mapM_.
This demonstrates that even without a traditional for loop, we can solve the problem simply by manipulating lists.
But what about Approaches 2 and 3, which seem to rely heavily on mutable state? Can we express those ideas in a pure functional language like Haskell?
Let’s find out.
Approach 2: Counting Up in Haskell
Can we write code like Approach 2, where a variable is incremented inside a loop, in Haskell?
Some people mistakenly believe that this is impossible because Haskell does not allow destructive assignment. In reality, however, we can express an imperative-style program by using the State monad (whether it’s the best approach is another question).
Let’s implement a loop in Haskell where the counter is incremented on each iteration and its corresponding value is printed.
Haskell Implementation of Approach 2
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State
main :: IO ()
main = do
evalStateT loop 1 -- Initialize the counter with 1
loop :: StateT Int IO ()
loop = do
cnt <- get -- Get the current counter value
if (cnt <= 30)
then do
-- Output
liftIO $ putStrLn $ convert cnt
-- Increment the counter
put (cnt + 1)
-- Repeat
loop
else return ()
where
convert :: Int -> String
convert n
| n `mod` 15 == 0 = "FizzBuzz"
| n `mod` 3 == 0 = "Fizz"
| n `mod` 5 == 0 = "Buzz"
| otherwise = show n
Doesn’t this look remarkably like imperative programming?
It reads very much like a program that describes how to compute the answer step by step. Even someone unfamiliar with Haskell’s syntax can probably get the general idea.
Incidentally, we can express the same idea without using the StateT monad transformer. Instead of storing mutable state, we simply pass the incremented counter as an argument during the recursive call.
Haskell Implementation of Approach 2 (Alternative)
main :: IO ()
main = loop 1
where
loop :: Int -> IO ()
loop cnt | cnt <= 30 && n > 0 = do
-- Output
putStrLn $ convert cnt
-- Increment the counter and recurse
loop (cnt + 1)
| otherwise = return ()
convert :: Int -> String
convert n
| n `mod` 15 == 0 = "FizzBuzz"
| n `mod` 3 == 0 = "Fizz"
| n `mod` 5 == 0 = "Buzz"
| otherwise = show n
Approach 3: Building a List Incrementally
So far we’ve looked at two different approaches.
In Approach 1, we transformed an entire list using map.
In Approach 2, we used the StateT monad transformer to express a loop that increments a counter and performs an IO action on each iteration.
Now let’s consider Approach 3.
The basic idea behind Approach 3 is to process the input one element at a time, gradually constructing the final list of output strings.
This is quite different from Approach 1, where we transform an entire collection by applying a function to every element.
In Haskell, one of the standard tools for accumulating results one element at a time is foldl (or foldr). foldl processes the list from left to right, while foldr processes it from right to left.
Here, we’ll use foldl.
Haskell Implementation of Approach 3
main :: IO ()
main = do
mapM_ putStrLn $ foldl fizzBuzz [] [1..30]
where
fizzBuzz :: [String] -> Int -> [String]
fizzBuzz l i
| mod i 15 == 0 = l ++ ["FIZZBUZZ"]
| mod i 3 == 0 = l ++ ["FIZZ"]
| mod i 5 == 0 = l ++ ["BUZZ"]
| otherwise = l ++ [(show i)]
This code is written to illustrate the underlying idea rather than to maximize efficiency.
At first glance, it doesn’t look very different from the map solution.
But notice what’s actually happening.
With the map approach, we apply a function of type Int -> String to transform an entire list of integers into a list of strings.
With foldl, however, we process the integers one by one, appending each converted string to the end of an accumulating list.
This reflects the mindset behind Approach 3. While the implementation is still purely functional, the way of thinking resembles the imperative approach.
Of course, Haskell is not performing destructive list updates internally, nor is this implementation equivalent to the Python version from Approach 3.
Nevertheless, we can still program with the idea of:
“Let’s examine each number one at a time and keep adding the converted result to the output list.”
The same idea can also be expressed with the State monad.
Haskell Implementation of Approach 3 (Alternative)
import Control.Monad.Trans.State
import Control.Monad (forM_)
main :: IO ()
main = do
mapM_ putStrLn $ execState loop []
-- Execute loop with [] as the initial state
-- Then print the resulting list
where
-- Loop that performs the conversion and appends to the list
loop :: State [String] ()
loop = do
forM_ [1..30] mkList
-- Executes:
-- mkList 1
-- mkList 2
-- ...
-- mkList 30
return ()
-- Convert a number and append it to the list
mkList :: Int -> State [String] ()
mkList i = do
m <- get
put (m ++ (convert i))
return ()
-- Convert an integer into its FizzBuzz representation
convert :: Int -> [String]
convert i
| mod i 15 == 0 = ["FizzBuzz"]
| mod i 3 == 0 = ["Fizz"]
| mod i 5 == 0 = ["Buzz"]
| otherwise = [(show i)]
Compared to the foldl version, this implementation may make the “append items one by one” concept a little easier to visualize.
I deliberately used forM_ instead of mapM_ because it more closely resembles the feel of a traditional for loop.
The overall idea is straightforward: inspect each value one at a time, convert it, and accumulate the results into a list.
So, What Was I Trying to Say?
You might be wondering, “So… what’s the point of all this?”
Fair question.
I don’t have a particularly profound argument to make. My main point is simply that we don’t need to become overly constrained by labels like functional programming or functional language.
I wrote this article as a small message of encouragement for those who think, “Since Haskell is a functional language, I have to abandon everything I know and completely change the way I think.”
Most books and articles about Haskell are written by incredibly knowledgeable people. As a result, they often emphasize abstract concepts, category theory, monads, and elegant mathematical models. Those topics are certainly valuable, but I sometimes feel they overshadow another important aspect of Haskell: it’s also just a practical programming language that can solve everyday problems.
Because Haskell is frequently introduced as the representative functional language, or praised for allowing mathematically elegant and concise programs, it’s easy to feel guilty when writing code that looks unapologetically imperative. Thoughts like:
- “I should think more functionally.”
- “There must be a more elegant solution.”
- “I need a more abstract model.”
can gradually become a source of unnecessary pressure.
Beauty and elegance certainly matter.
But sometimes a straightforward, even slightly messy, solution is exactly what’s needed to solve a real problem.
That was the motivation behind this article.
By using the FizzBuzz problem, I wanted to show that even approaches which initially seem to require mutable state—or appear to belong to the imperative paradigm—can often be expressed perfectly well in Haskell.
If you know that Haskell allows multiple ways of approaching a problem, it becomes much easier to relax. Of course, if you can immediately write a beautiful, idiomatic functional solution, that’s wonderful.
But if striving for the “perfect” functional solution causes you to give up before you’ve written anything at all, then perhaps it’s better to start with a more straightforward approach.
If this article gives even one person the confidence to think, “Maybe I can write this in Haskell after all,” then I’ll consider it worthwhile.
Final Thoughts
Thank you for reading such a long article.
I realize my thoughts have wandered a bit, and I appreciate you sticking with me until the end.
Most of all, I hope you enjoy writing Haskell.