Fizz Buzz Through Monoids
Some decade ago I read a good implementation of fizzbuzz. What set it apart was its excellent modularity. The original article is no longer on the web1 Although I realised just now it is archived., but this is my reconstruction:
module Main where import Control.Monad (guard) import Data.Foldable (for_) import Data.Maybe (fromMaybe) fizzbuzz i = fromMaybe (show i) . mconcat $ [ "fizz" <$ guard (rem i 3 == 0) , "buzz" <$ guard (rem i 5 == 0) ] main = for_ [1..100] $ putStrLn . fizzbuzz
The great thing about this implementation is that if we get the natural change in requirements – that we are supposed to print “zork” for multiples of seven – we can accomodate that change by simply adding the line that does so:
--- orig.hs 2025-05-23 13:11:29.929652707 +0200 +++ new.hs 2025-05-23 13:18:02.897544701 +0200 @@ -8,6 +8,7 @@ fromMaybe (show i) . mconcat $ [ "fizz" <$ guard (rem i 3 == 0) , "buzz" <$ guard (rem i 5 == 0) + , "zork" <$ guard (rem i 7 == 0) ] main =
This will combine perfectly well with the other printouts, and we don’t have to change any other place in the code.
This is an indication of good modularity, and there are surprisingly few implementations of fizzbuzz that allow this. I’m serious. Try for yourself!
Monoids are the magic
We have previously seen the guard-sequence pattern which sits at the core of this implementation. The expression
"fizz" <$ guard (rem i 3 == 0)
will evaluate to Just "fizz"
whenever i
is divisible with three, and in all
other cases it evaluates to Nothing
. Thus, if a number is not divisible by any
of the three given, the list will evaluate to
[Nothing, Nothing, Nothing]
If a number is divisible by only five, but not three or seven, the list will be
[Nothing, Just "buzz", Nothing]
And if a number is divisible by, say, three and seven, but not five, the list will be
[Just "fizz", Nothing, Just "zork"]
These are smushed together by the mconcat
from the Monoid
interface, which
uses the generic smushing operation <>
. This behaves just as expected for our
strings-that-might-not-exist: it concatenates them together if they do exist,
otherwise it returns Nothing
.
Whatever we get out of mconcat
, we pass it to fromMaybe (show i)
which
replaces any Nothing
values with the string representation of the number
coming into the function, but passes through any actual values it receives
intact. This is the full fizzbuzz
function that converts a number to the
correct textual representation.
To make it an actual program, we loop through all numbers [1..100]
, convert
them with fizzbuzz
, and print the result.
Why you should care about fizzbuzz with monoids
It’s not that we care particularly much about fizzbuzz as a problem, but this
implementation is a great example of the sort of power that’s unlocked when
the standard libraries contain the right generic interfaces (like Monoid
and
Alternative
.)
We get this modularity for free when writing fizzbuzz – but not only when writing fizzbuzz! It happens also on greater scales.