🎉 Announcing new lower pricing — up to 40% lower costs for Cloud Servers and Cloud SQL! Read more →

Functional FizzBuzz in Elm and Ruby

It’s a Bank Holiday here in the UK and that’s the traditional time when Systems People take to their retreats and work on their Foo.

Since Functional Programming is all the rage at the moment I thought I’d take everybody’s favourite interview test and give it an FP makeover.

We’ll use elm to start with because it’s a nice, simple ML language and has a neat REPL with great error messages if you mess up:

> foobar: String
| foobar = 14
|   
-- TYPE MISMATCH ---------------------------------------------------------- REPL

Something is off with the body of the `foobar` definition:

10| foobar = 14
             ^^
The body is a number of type:

    number

But the type annotation on `foobar` says it should be:

    String

Hint: Try using String.fromInt to convert it to a string?

>

The Problem

Before we start hacking, let’s have a look at the problem specification.

Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.

What we have here is a conditional transform of a list of integers. And transforms are what FP is really good at. So let’s break it down into primitives, FP style, then build them up into a solution.

Elm

First we need a function that takes an integer and produces a string if the integer is a multiple of three. That bit is straightforward. The question is what should that function produce if the integer isn’t a multiple of three?

Well, nothing.

In other words when we call this function, we may get a string and we may get nothing. We can model that in elm very easily using the Maybe type.

Let’s give elm the type specification we want

> fizz: Int -> Maybe String
| fizz

and then complete the definition

> fizz: Int -> Maybe String
| fizz x = if modBy 3 x == 0 then Just "Fizz" else Nothing
|   
<function> : Int -> Maybe String
>

The definition for Buzz is pretty much the same

> buzz: Int -> Maybe String
| buzz x = if modBy 5 x == 0 then Just "Buzz" else Nothing
|   
<function> : Int -> Maybe String
>

Let’s check they do what we expect

> fizz 10
Nothing : Maybe String
> fizz 6
Just "Fizz" : Maybe String
> buzz 10
Just "Buzz" : Maybe String
> buzz 6
Nothing : Maybe String
> buzz 15
Just "Buzz" : Maybe String
> fizz 15
Just "Fizz" : Maybe String
>

Lovely. All as expected.

Now Breathe and Abstract

But we can do better than that. Note how similar the two function definitions are. That means there is a more abstract function that can do both. What we need is a function that takes a divisor as a number and an output as a string as well as the number to check. Then we can do both fizz and buzz with the same function. Let’s pass these meta-parameters in as a tuple.

> fbTransform: Int -> (Int, String) -> Maybe String
| fbTransform x (mod, val) =
|   if modBy mod x == 0 then Just val else Nothing
|   
<function> : Int -> ( Int, String ) -> Maybe String
>

When we test we get

> fbTransform 6 (3, "Fizz")
Just "Fizz" : Maybe String
> fbTransform 6 (5, "Buzz")
Nothing : Maybe String
> fbTransform 10 (3, "Fizz")
Nothing : Maybe String
> fbTransform 10 (5, "Buzz")
Just "Buzz" : Maybe String
>

The fizz and buzz checks we need can be reduced to a simple list of tuples

> fizzBuzzMetaArguments: List (Int, String)
| fizzBuzzMetaArguments = [(3, "Fizz"), (5, "Buzz")]
|   
[(3,"Fizz"),(5,"Buzz")]
    : List ( Int, String )
>

and we can map those arguments across the transform function to get the List of output strings we want

> fizzBuzzMetaArguments |> List.map (fbTransform 30)
[Just "Fizz",Just "Buzz"]
    : List (Maybe String)
> fizzBuzzMetaArguments |> List.map (fbTransform 10)
[Nothing,Just "Buzz"] : List (Maybe String)
>

From this we can create a function that takes the meta arguments along with the number to check and returns a List String, using filterMap instead of map to pull the values out of the Maybe types. Once we have that List we can manipulate it down into the output string we want.

To do that we need a couple of convenience functions. Firstly, a function to generate the default of displaying the Integer as a String.

> orShow: Int -> Maybe String -> String
| orShow = String.fromInt >> Maybe.withDefault
|   
<function> : Int -> Maybe String -> String
>

and, secondly, a function to switch a string to a Maybe based upon whether the String has anything in it

> stringNotEmpty: String -> Maybe String
| stringNotEmpty val = if String.isEmpty val then Nothing else Just val
|   
<function> : String -> Maybe String
>

then we can stream all the transformations neatly together

> fizzBuzzerCategory: List (Int, String) -> Int -> String
| fizzBuzzerCategory metaArgumentList x =
|   metaArgumentList
|   |> List.filterMap (fbTransform x)
|   |> String.concat
|   |> stringNotEmpty
|   |> (orShow x)
|   
<function> : List ( Int, String ) -> Int -> String
>

which we use to curry up the final transform function for FizzBuzz

> fizzbuzzer = fizzBuzzCategory fizzBuzzMetaArguments
<function> : Int -> String
>

Final check

 > fizzbuzzer 4
"4" : String
> fizzbuzzer 6
"Fizz" : String
> fizzbuzzer 10
"Buzz" : String
> fizzbuzzer 30
"FizzBuzz" : String
>

So far so good. Now let’s bring it all together in a final FP flourish

 > List.range 1 100 |> List.map fizzbuzzer
["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"] : List String
>

Tada.

Then the Curve Ball

The second part of the test arrives

now alter the program so that multiples of 7 print “Woof”

No problem

> fizzbuzzer = fizzBuzzerCategory (fizzBuzzMetaArguments ++ [(7, "Woof")])
<function> : Int -> String
> List.range 1 100 |> List.map fizzbuzzer
["1","2","Fizz","4","Buzz","Fizz","Woof","8","Fizz","Buzz","11","Fizz","13","Woof",
"FizzBuzz","16","17","Fizz","19","Buzz","FizzWoof","22","23","Fizz","Buzz","26",
"Fizz","Woof","29","FizzBuzz","31","32","Fizz","34","BuzzWoof","Fizz","37","38",
"Fizz","Buzz","41","FizzWoof","43","44","FizzBuzz","46","47","Fizz","Woof","Buzz",
"Fizz","52","53","Fizz","Buzz","Woof","Fizz","58","59","FizzBuzz","61","62","FizzWoof",
"64","Buzz","Fizz","67","68","Fizz","BuzzWoof","71","Fizz","73","74","FizzBuzz","76","Woof",
"Fizz","79","Buzz","Fizz","82","83","FizzWoof","Buzz","86","Fizz","88","89","FizzBuzz","Woof",
"92","Fizz","94","Buzz","Fizz","97","Woof","Fizz","Buzz"] : List String
>

Done

Ruby

You don’t need to use a pure functional language to take advantage of this approach. Ruby, for example, has many of the features of a functional language, with more arriving with every version.

Here’s the Functional Fizzbuzz approach in Ruby.

def fb_transform(x, (mod, val))
  val if (x % mod).zero? 
end

def fizz_buzz_arguments
  [
    [3, 'Fizz'],
    [5, 'Buzz']
  ]
end

def fizzbuzzercategory(meta, x)
  case meta.filter_map { |arr| fb_transform(x, arr) }
  in []
    x.to_s
  in arr
    arr.join
  end
end

def fizzbuzzer(x)
  fizzbuzzercategory(fizz_buzz_arguments, x)
end

def run
  (1..100).map { |x| fizzbuzzer(x) }
end

puts
puts run.inspect

def fizzbuzzer(x)
  fizzbuzzercategory(fizz_buzz_arguments << [7, 'Woof'], x)
end

puts
puts run.inspect

Running this gives

$ ruby fizzbuzz.rb 
fizzbuzz.rb:21: warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!

["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"]

["1", "2", "Fizz", "4", "Buzz", "Fizz", "Woof", "8", "Fizz", "Buzz",
"11", "Fizz", "13", "Woof", "FizzBuzz", "16", "17", "Fizz", "19",
"Buzz", "FizzWoof", "22", "23", "Fizz", "Buzz", "26", "Fizz",
"Woof", "29", "FizzBuzz", "31", "32", "Fizz", "34", "BuzzWoof",
"Fizz", "37", "38", "Fizz", "Buzz", "41", "FizzWoof", "43", "44",
"FizzBuzz", "46", "47", "Fizz", "Woof", "Buzz", "Fizz", "52", "53",
"Fizz", "Buzz", "Woof", "Fizz", "58", "59", "FizzBuzz", "61", "62",
"FizzWoof", "64", "Buzz", "Fizz", "67", "68", "Fizz", "BuzzWoof",
"71", "Fizz", "73", "74", "FizzBuzz", "76", "Woof", "Fizz", "79",
"Buzz", "Fizz", "82", "83", "FizzWoof", "Buzz", "86", "Fizz", "88",
"89", "FizzBuzz", "Woof", "92", "Fizz", "94", "Buzz", "Fizz", "97",
"Woof", "Fizz", "Buzz"]

Note how I’ve sneaked in the new Ruby 2.7 pattern matching syntax in the case statement, argument deconstruction and a filterMap. Bonus Foo for reading this far!

Foo Increased!

And there we have it. A slightly different take on an old problem. A Goldilocks version if you like. Not too Abstract. Not too Concrete. Just right.

After a few more decades of Foo Quests like this I might get close enough to Nirvana to really understand Lisp.

Try Brightbox for free

If you want to quickly fire up an Ubuntu Focal image so you can play with elm or Ruby 2.7, you can sign up for Brightbox in just a couple of minutes and use your ÂŁ50 free credit to give it a go.

Get started with Brightbox Sign up takes just two minutes...