Fizz Buzz Without Booleans
My programming languages professor Eugene Wallingford recently responded to a post by software developer Evan Hahn, who in turn was relaying this coding challenge from a podcast:
Write Fizz Buzz with no booleans, no conditionals, no pattern matching, or other things that are like disguised booleans.
A Fizz Buzz solutions shows numbers that are multiples of 3 and 5 as FizzBuzz, multiples of only 3 as Fizz, multiples of only 5 as Buzz, and every other number as just itself.
Eugene's vehicle to teach me programming language ideas was Scheme. When my turn to teach this class came around, I tried Racket for one year. Ever since I've used Haskell because it was even more brain-breaking with its pattern matching, automatic currying, and static typing. I didn't read the prompt closely and thought, “We can use pattern matching in Haskell!”
At the heart of Evan's solution is the cycle function that repeats a list as one iterates beyond its elements. If we simultaneously iterate through a list of 3 elements and 5 elements, we can apply some operation that distinguishes between multiples of 3, 5, and 15. My initial strategy was to wrap up the value with tags that marked its fizziness and buzziness. For that I used this enum:
data Value
= Fizz Value
| Buzz Value
| Pass Value
| Number Int
main = do
let fizzes = cycle [Pass, Pass, Fizz]
let buzzes = cycle [Pass, Pass, Pass, Pass, Buzz]
mapM_ print $ take 30 $ map (\(f, g, x) -> f . g . Number $ x) $ zip3 fizzes buzzes [1..]
data Value = Fizz Value | Buzz Value | Pass Value | Number Int main = do let fizzes = cycle [Pass, Pass, Fizz] let buzzes = cycle [Pass, Pass, Pass, Pass, Buzz] mapM_ print $ take 30 $ map (\(f, g, x) -> f . g . Number $ x) $ zip3 fizzes buzzes [1..]
The value 2 would get wrapped as Pass $ Pass $ Number 2. The value 3 as Fizz $ Pass $ Number 3. The value 5 as Pass $ Buzz $ Number 5. The value 15 as Fizz $ Buzz $ Number 15. So far, so good. Then I added two functions that pattern-matched on the variants to form the expected string:
showZz (Fizz value) = "Fizz" ++ showZz value
showZz (Buzz value) = "Buzz" ++ showZz value
showZz _ = ""
instance Show Value where
show (Pass value) = show value
show (Number value) = show value
show value = showZz value
showZz (Fizz value) = "Fizz" ++ showZz value showZz (Buzz value) = "Buzz" ++ showZz value showZz _ = "" instance Show Value where show (Pass value) = show value show (Number value) = show value show value = showZz value
The Fizz and Buzz variants get channeled off into showZz, which don't show the number.
Since code is never done, I tried thinking of other solutions. Then I re-read the prompt and saw that my first solution broke the rules. Eugene also pointed this out. I think a smart compiler could turn pattern matching into dynamic dispatch, which has no conditional logic, but it was expressly forbidden. Plus my reputation with my professor from 2002 was on the line.
While out driving with my son to get his driving practice hours in and fretting about my survival, I came up with a solution that uses arithmetic rather than type tags to mark each number:
pick :: (Int, Int, Int) -> String
pick (f, b, n) = [show n, "Fizz", "Buzz", "FizzBuzz"] !! (f + b)
main = do
let fizzes = cycle [0, 0, 1]
let buzzes = cycle [0, 0, 0, 0, 2]
mapM_ putStrLn $ take 30 $ map pick $ zip3 fizzes buzzes [1..]
pick :: (Int, Int, Int) -> String pick (f, b, n) = [show n, "Fizz", "Buzz", "FizzBuzz"] !! (f + b) main = do let fizzes = cycle [0, 0, 1] let buzzes = cycle [0, 0, 0, 0, 2] mapM_ putStrLn $ take 30 $ map pick $ zip3 fizzes buzzes [1..]
The number 2 gets paired with 0. The number 3 with 1. The number 5 with 2. The number 15 with 3. We use that number as an index to pull out the correct string from a list.