soxfox

Raindrops is the latest featured exercise of #48in24, and the final featured exercise of January – the warm-up month. Raindrops is a simple variation on the classic coding interview question/children’s game FizzBuzz. The original task asks coders to print the integers from 1 to 100, but replace multiples of three with “Fizz”, multiples of five with “Buzz”, and multiples of both with “FizzBuzz”. Exercism’s version of this task also includes multiples of seven, and replaces “Fizz” and “Buzz” with the sounds of raindrops. It also removes the looping requirement, and now just expects the code to take a number and return the corresponding raindrop string.

This week, since the exercise is pretty easy, I’m choosing to keep things entertaining (for myself at least) by solving it in each featured language using a notable feature of that language.

Languages

Ruby

Ruby is an interpreted language created by Matz (Yukihiro Matsumoto) in 1995. It has strong roots in both Perl and Smalltalk, though over time some of the more Perl-like features have fallen out of use. Thanks to its Smalltalk inspiration, it’s a very object-oriented language, with a preference for duck-typing rather than strong types.

For this first solution, I’m (unnecessarily) using object-oriented programming, metaprogramming, and a touch of functional programming (wait, it’s not Functional February yet!).

Sound = Struct.new(:divisor, :sound) do
  def of(number)
    number % divisor == 0 ? sound : ""
  end
end

class Raindrops
  SOUNDS = [Sound.new(3, "Pling"), Sound.new(5, "Plang"), Sound.new(7, "Plong")]
  def self.convert(number)
    sound = SOUNDS.map { |sound| sound.of(number) }.join
    sound.empty? ? number.to_s : sound
  end
end

First, I make a Sound class, but instead of using Ruby’s usual class syntax and providing attributes and a constructor manually, I’ve used Struct to handle this for me. Struct::new creates a subclass of Struct with the given attributes, and allows additional methods to be defined on this subclass with a block. Here, one new method is added (Sound#of) which returns the string to be added for a specific number – either the given sound, or a blank string.

The bulk of the solution lives in Raindrops. It contains a constant list with the three Sounds required for this task. In Raindrops::convert, map is used to convert the list of Sounds to a list of strings, demonstrating the syntax for anonymous functions (blocks) in Ruby. Finally, join these strings and return either the joined string, or if no sounds were made, the original number converted to a string.

What I found most interesting about this solution was the metaprogramming involved in Struct. A class (which is also an object) has a method to create a new subclass with its own methods – maybe don’t think too hard about it. I can definitely see how this makes Ruby as a language highly extensible.

R

R is the first featured language with a single-character name. It’s a language primarily focused on statistics, data analysis, and data visualisation, and is part of the GNU Project. The language feature that caught my eye for Raindrops was vectorisation, which means that many operations in R can be applied to more than one piece of data at a time.

raindrops <- function(number) {
  sounds <- c("Pling", "Plang", "Plong")[number %% c(3, 5, 7) == 0]
  if (length(sounds) == 0) return(as.character(number))
  paste(sounds, collapse="")
}

I’ll take this one step at a time:

R is perhaps not well suited to general purpose programming, but I think for this exercise it worked well. Vectorised operations are very powerful, which is why they can be found in many data processing libraries for other languages – they are expressive, and can have some major performance benefits.

Common Lisp

The parentheses have returned! For anyone who was annoyed that I only used closing parentheses when joking about Lisp in Week 1, enjoy these opening ones to balance it out: (((((((. Common Lisp is much older than Clojure, which was featured in week 1. One of the interesting features of Lisps is their powerful macro support, and I really wanted to do something with that for Raindrops.

What I came up with was the following macro:

(defmacro def-fizzbuzz (name &rest pairs)
  `(defun ,name (number)
     (let ((sound
            (concatenate 'string
                         ,@(loop for (a b) on pairs by #'cddr
                                 collect `(if (zerop (mod number ,a)) ,b "")))))
          (if (zerop (length sound)) (write-to-string number) sound))))

This macro can solve not just Raindrops, but FizzBuzz, and in fact any similar problem with any choice of factors. Even better, it generates the code to do so at compile-time - that’s the power of macros!

The main power in this macro is that it can take multiple factor-word pairs, and expand that into multiple lines of code. This is handled by the loop call, which is itself a very powerful macro. Lisp’s loop is incredibly flexible, and I’ve used just a fraction of its options here. Specifically, I used for and on to pattern-match the first two items of the list, by with cddr to remove those items from the list, and collect to build a new list by evaluating the next expression for each iteration of the loop.

I’m not going to dig into every part of how this macro works, but I will provide a quick overview of some of the stranger syntax:

The macro can be used as follows:

(def-fizzbuzz convert
  3 "Pling"
  5 "Plang"
  7 "Plong")

which expands to:

(defun convert (number)
  (let ((sound
        (concatenate 'string 
                     (if (zerop (mod number 3)) "Pling" "")
                     (if (zerop (mod number 5)) "Plang" "")
                     (if (zerop (mod number 7)) "Plong" ""))))
       (if (zerop (length sound)) (write-to-string number) sound)))

The expanded function overall works very similarly to the previous solutions - join one or more strings together to form the sound, then return the sound, or the original number as a string if there isn’t a sound.

This was a pretty interesting challenge, though I don’t know if I would use this in production. I prefer more modern Lisps, but Common Lisp has some pretty cool features that I would like to see in more languages - loop is one, and the insanely over-the-top format macro is another.

Final Thoughts

I really enjoyed playing with some of the more distinct features of these languages, and I’ll keep trying to do that, at least for the simpler exercises.

Lisp macros are always bit of a fun puzzle to figure out, and I’m hoping to get a chance to use similar macros in a non-Lisp language, since they aren’t exclusive to Lisp. I’ve used Ruby before, so there was less to surprise me there, but I do like the functional parts of my solution at least. I’m excited to see how R will be used in #48in24, because it probably won’t fit as a completely general purpose language, but vectorisation could be useful again.

I hope you enjoyed this exploration of some very different languages, and that I was able to show you something new! Feel free to send any corrections or suggestions you have to , and keep an eye out for Week 4!