Let’s see how much Haskell you can understand just by first grasping an informal, hand-wavy solution and then looking at the corresponding code. It’s very much a “think in data” solution, but also probably quite different from how you’ve seen this kind of problem approached before. Without modulo division or if statements, I give you “Functional Fizz Buzz.”
The task is this: you want Fizz every three steps, and Buzz every five steps. We note that sometimes the cycles coincide. Let’s talk cycles then.
| threes = cycle ["", "", "Fizz"] |
| fives = cycle ["", "", "", "", "Buzz"] |
cycle is defined like this (the real library def is more efficient, but less clear):
| cycle xs = xs ++ cycle xs -- SIMPLE version of lib |
So threes just spits out ["","","Fizz","","","Fizz",... ] until we stop it, and similarly for fives. Next, we want to merge two streams into one: this is quite common, so there’s a library function for it. zipWith pairs up elements and uses some operation to combine each pair:
| zipWith g [a,b,c,...] [d,e,f, ...] ===> |
| (computes to) [g a d, g b e, g c f, ...] |
| eg zipWith max [1,2,3] [2,2,2] ===> [2,2,3] |
| eg zipWith (*) [1,2,3] [2,2,2] ===> [2,4,6] |
Think zippers in clothes. Now, that’s just what we want for merging our streams. It works for infinite streams too (why shouldn’t it?).
| fizzbuzz = zipWith (++) threes fives |
(++) is string concatenation, and then we just push the list of lines to the screen. And hit ^C when we get bored.
| main = putStr (unlines fizzbuzz) |
If we want numbers in there between the Fizzes and Buzzes instead of blanks, we can just zip in another list that contains the numbers from 1 up to infinity, and just add in a number if the string would otherwise be empty.
So, it’s a short piece of code that obviously works, built from small pieces and glued together in a simple way (think Unix pipes), and there’s no worry about loops, division, variables, memory limits...
This isn’t a one-off trick. Programming with Haskell is generally like this.