Dwi Wahyudi

Senior Software Engineer (Ruby, Golang, Java)


This post will show my approach on handling fizzbuzz in Elixir. We will look at some features in Elixir programming language: Guard, pipe operator and first-class functions.

Overview

Previously, I’ve written fizzbuzz in Golang here,

Now let’s write fizzbuzz in Elixir. https://elixir-lang.org.

Fizzbuzz is a simple problem, but it is quite good at showing us how a programming language will do conditions (if-else) and iterations (loop).

In Elixir, there is no class (so there would be no instance variable), we use functions to do everything. Here, we will write a module (which we use to group functions).

defmodule Fizzbuzz do

end

Nothing really special, just an empty module.

Elixir Guard

We have seen Golang uses simple for loop or for-each loop and some classical if-else syntax. Java and Ruby are mostly the same. But in Elixir, there is no for-loop (we use recursion instead), and although there is if-else syntax, idiomatic Elixir uses Guard instead.

Now, what we gonna do? Just as I wrote above, we just need to use Guard.

  • Guard is conditions placed on functions.
  • Function will run if Guard conditions are satisfied.

Elixir guards

Let say we have a function named calculate_discount, and it has params cost. Let say calculate discount here needs to run only if cost is more than 500. So the function will look like this:

  def calculate_discount(cost) when cost > 500 do
    # do something with the cost if cost is > 500
  end

What if we have multiple conditions? In Java/Ruby/Golang we will create if-else, but in Elixir, with Guard, just create another function (with the same name) but with different Guard conditions.

  def calculate_discount(cost) when cost > 500 do
    # do something, if cost > 500
  end

  def calculate_discount(cost) when cost > 2000 do
    # do something, if cost > 2000
  end
  
  def calculate_discount(cost) do
    # the else condition, if none of above functions Guard satisfies
    # in other words, do something if cost <= 500
  end

With this in mind, we can solve our fizzbuzz in Elixir with 4 functions. Because if we look at Go Code there are 4 conditions.

  • When fizzbuzz (divisible by 3 and 5).
  • When fizz only (divisible by 3).
  • When buzz only (divisible by 5).
  • When neither fizz nor buzz, so return the number itself.

Our code in Elixir, will be like this, we will need rem function to calculate remainder of division.

defmodule Fizzbuzz do

  def of(num) when rem(num, 3) == 0 and rem(num, 5) == 0, do: "FizzBuzz"
  def of(num) when rem(num, 3) == 0, do: "Fizz"
  def of(num) when rem(num, 5) == 0, do: "Buzz"
  def of(num), do: num

end

Calling these functions is simple:

Fizzbuzz.of(3) # "Fizz"
Fizzbuzz.of(9) # "Fizz"

Fizzbuzz.of(5) # "Buzz"
Fizzbuzz.of(100) # "Buzz"

Fizzbuzz.of(15) # "FizzBuzz"
Fizzbuzz.of(30) # "FizzBuzz"

Fizzbuzz.of(2) # 2

Pipe Operator

Pipe operator |> is pretty simple to explain as it pipe the value into the first param of the function

  a |> b()

  # is the same as:
  b(a)
  
  c |> d(e)

  # is the same as:
  d(c, e)
  
  order_data
    |> calculate_discount()
    |> calculate_tax()
    |> calculate_delivery_price(delivery_price)
    
  # is the same as:
  calculate_delivery_price(calculate_tax(calculate_discount(order_data)), delivery_price)

First Class Functions

Now we have those 4 of functions in place, we can easily create a collection function that will receive num_start and num_end as range, so we can get fizzbuzz from 1 to 1000 for example.

Roughly speaking, this collection function will iterate from num_start to num_end and call Fizzbuzz.of(num) method multiple times, and collect/map these values into a list.

Elixir is a functional programming language, which means functions are first-class. https://en.wikipedia.org/wiki/First-class_function

We can pass of function as arguments to other functions.

  def collection(num_start, num_end) do
    (num_start..num_end)
    |> Enum.map(&of(&1))
  end

And we can call the function like this:

Fizzbuzz.collection(1, 20)
# [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz"]

Our Fizzbuzz in Elixir is already completed. But what’s going on that collection function?

(num_start..num_end) is range data, which will return list of numbers from num_start to num_end. We pipe this list into Enum.map/2 function.

Enum.map is built-in Elixir function. https://hexdocs.pm/elixir/Enum.html#map/2.

It takes 2 params.

  • The data that we operate our function on each of them.
  • The function.

That collection function can be written verbosely like this:

  def collection(num_start, num_end) do
    (num_start..num_end)
    |> Enum.map(fn(x) -> of(x) end)
  end

Because of an Elixir feature, capture function, we can instead write call to Enum.map, like this:

    |> Enum.map(&of(&1))

&1 is the param which is in the first position/order that we want to send to of function. We capture the of function, and send as argument each value of (num_start..num_end).

Reference:

https://elixir-lang.org/getting-started/modules-and-functions.html#function-capturing