FizzBuzz kata in Clojure by Mark Seemann
This post describes my first experience with doing the FizzBuzz kata in Clojure.
After having looked at Clojure for some time, I finally had a bit of time to play with it, so I decided to do the FizzBuzz kata in Clojure.
Single fizzbuzz function #
Clojure has a built-in testing framework, so I wrote a Parameterized Test for a single fizzbuzz function. Using TDD, I added test cases a little at a time, attempting to follow the Transformation Priority Premise, but the end result is this:
(ns fizzbuzz.core-test (:use clojure.test fizzbuzz.core)) (deftest fizzbuzz-test (are [expected i] (= expected (fizzbuzz i)) "1" 1 "2" 2 "Fizz" 3 "4" 4 "Buzz" 5 "Fizz" 6 "7" 7 "8" 8 "Fizz" 9 "Buzz" 10 "11" 11 "Fizz" 12 "13" 13 "14" 14 "FizzBuzz" 15 "FizzBuzz" 30))
Clojure syntax is somewhat backwards from what I'm normally used to, but the are macro expands into a collection of tests that each evaluate whether the result of invoking the fizzbuzz function with i
is equal to expected
.
Through a series of transformations of the SUT, I ended up with this implementation of the fizzbuzz function:
(ns fizzbuzz.core) (defn fizzbuzz [i] (cond (= 0 (mod i 15)) "FizzBuzz" (= 0 (mod i 3)) "Fizz" (= 0 (mod i 5)) "Buzz" :else (str i)))
This defines a function called fizzbuzz taking a single argument i
. The cond macro evaluates each test and returns the expression associated with the first test that evauluates to true. The first test checks if i
is divisible with 15 and returns "FizzBuzz" if this is the case; the next test checks if i
is divisible with 3 and returns "Fizz" if this is true, and so on.
Printing a range of fizzbuzz values #
The task defined by the kata is to print all FizzBuzz values from 1 to 100, so the above function is't the final solution. The next step I took was to write a test that defines a version of the fizzbuzz function taking two parameters:
(def acceptance-expected "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz") (deftest acceptance-test (is (= acceptance-expected (fizzbuzz 1 101))))
I decided to define the acceptance-expected
value outside of the test case itself, as I thought that made the test a bit more readable. The test case is defined by the is macro and states that the expected value is acceptance-expected
and the actual value is the result of invoking the fizzbuzz function with two arguments: 1 as the (inclusive) start value, and 101 as the (exclusive) end value. The above code listing of fizzbuzz only accept one argument, but the new test case requires two arguments, so I added an overload to the function:
(defn fizzbuzz ([i] (cond (= 0 (mod i 15)) "FizzBuzz" (= 0 (mod i 3)) "Fizz" (= 0 (mod i 5)) "Buzz" :else (str i))) ([start end] (apply str (interpose "\n" (map fizzbuzz (range start end))))))
The previous implementation is still there, now contained within the overload taking a single argument i
, but now there's also a new overload taking two arguments: start
and end
.
This overload generates a sequence of integers from start
to end
using the range function. It then maps that sequence of integers into a sequence of strings by mapping each integer to a string with the fizzbuzz function. That gives you a sequence of strings such as ("1" "2" "Fizz" "4" "Buzz")
.
In order to print all the FizzBuzz strings, I need to interpose a newline character between each string, which produces a new sequence of strings such as ("1" "\n" "2" "\n" "Fizz" "\n" "4" "\n" "Buzz")
. To concatenate all these strings, I apply the str function to the sequence.
Printing FizzBuzz values from 1 to 100 #
The requirements of the kata is to print all FizzBuzz values from 1 to 100, and the code already does this. However, I interpret the kata as requiring a single function that takes no parameters, so I added an acceptance test case:
(deftest acceptance-test (is (= acceptance-expected (fizzbuzz 1 101))) (is (= acceptance-expected (fizzbuzz))))
Notice the second test case in the last line of code that invokes the fizzbuzz function without any parameters. It's easily resolved by adding a third overload:
(defn fizzbuzz ([] (fizzbuzz 1 101)) ([i] (cond (= 0 (mod i 15)) "FizzBuzz" (= 0 (mod i 3)) "Fizz" (= 0 (mod i 5)) "Buzz" :else (str i))) ([start end] (apply str (interpose "\n" (map fizzbuzz (range start end))))))
As you can see, the first overload takes no parameters and simply invokes the previously desribed overload with the start
and end
arguments.
FWIW, this entire solution is structurally similar to my implementation of FizzBuzz in F#.