Over-Engineering FizzBuzz

The main language I use in my day-to-day programming life is Common Lisp. It’s a wonderful language with some very powerful tools that most other languages don’t have. How many other languages have the powerful macro system of Lisp? How about generic functions? Not many.


Side note: A generic function is a function you can have many different version of which use a type system to determine which version should be called. This isn’t completely true, but good enough for what I’m writing here.


With this much power we can write code more complex than it ever should be. Let’s use FizzBuzz for an example. The goal of FizzBuzz is to print the numbers from 1 to 100 where if the number is divisible by 3 we print “Fizz”, if it’s divisible by 5 we print “Buzz
and if it’s divisible by 3 and 5 we print “FizzBuzz”. It’s a classical interview problem and now an interview trope.

First, let’s do a simple macro example. I don’t want recursion or multiple function calls or loop iteration in my code, in this example. So I can make a macro that will unroll into a sequence of print statements.

(defmacro stupid-fizz-buzz (c)
  (cond ((> c 100) ())
        ((zerop (mod c 15))
          `(progn
             (print "FizzBuzz")
             (stupid-fizz-buzz ,(1+ c))))
        ((zerop (mod c 3))
          `(progn
             (print "Fizz")
             (stupid-fizz-buzz ,(1+ c))))
        ((zerop (mod c 5))
          `(progn
             (print "Buzz")
             (stupid-fizz-buzz ,(1+ c))))
        (t
          `(progn
             (print ,c)
             (stupid-fizz-buzz ,(1+ c))))))

Changing 100 to 3 and calling macroexpand-all on (stupid-fizz-buzz 1) we get:

(PROGN (PRINT 1) (PROGN (PRINT 2)
  (PROGN (PRINT "Fizz") NIL)))

There are nicer ways to write stupid-fizz-buzz as a macro, but this is a dead simple way.

Also calling (let ((n 1)) (stupid-fizz-buzz n)) won’t work because n isn’t an integer at the time of macro expansion, so some care must be taken. In order for the macro to work the input must be an integer at time of macro-expansion. To fulfill the problem we could give the below inlined function and we should see the unrolled code wherever we call fizz-buzz in our code after compilation.

(declaim (inline fizz-buzz))
(defun fizz-buzz ()
(stupid-fizz-buzz 1))

Perhaps you believe one function should print “Fizz” and another function should print “Buzz”. Also, you love generic functions.

(defparameter *fizz* 3)
(defparameter *buzz* 5)
(defparameter *up-to* 100)

(defgeneric %stupid-fizz-buzz (count))

(defmethod %stupid-fizz-buzz :before ((count integer))
  (when (zerop (mod count *fizz*))
    (format t "Fizz")))

(defmethod %stupid-fizz-buzz :before ((count rational))
  (when (zerop (mod count *buzz*))
    (format t "Buzz")))

(defmethod %stupid-fizz-buzz (count)
  (if (or (zerop (mod count *fizz*))
          (zerop (mod count *buzz*)))
      (format t "~%")
      (format t "~a~%" count))
  (when (< count *up-to*)
    (%stupid-fizz-buzz (1+ count))))

(defun stupid-fizz-buzz ()
  (%stupid-fizz-buzz 1))

 

Here, integer is more exact than rational so the "Fizz" will occur before the "Buzz". Either way, entirely over-engineered… At least the macro version has the benefit of complete loop unrolling.


How should fizz buzz be done.

(defun fizz-buzz ()
  (loop for i from 1 to 100 do
    (when (zerop (mod i 3))
      (format t "Fizz"))
    (when (zerop (mod i 5))
      (format t "Buzz"))
    (if (or (zerop (mod i 3))
            (zerop (mod i 5))))
        (format t "~%")
        (format t "a~%" i))))

Is probably what it should be, you can do a bit nicer if you understand the format directive better.


I hope you had fun in this silly post.

Shout out to @cgay for some spelling errors and the note about the macro not working for (let ((n 1)) (stupid-fizz-buzz n)) .

There’s more examples: https://www.reddit.com/r/lisp/comments/59ikqm/the_most_elegant_implementation_of_fizzbuzz/

Finally, Little one:

img_20200514_130303

Leave a comment