cond

The cond macro lets you list out a series of conditions, each with associated code. It executes the code corresponding to the first truthy conditions.

In the game of FizzBuzz, children count up from 1. If the number is a multiple of three, they say “Fizz.” For multiples of five, they say “Buzz.” For multiples of both, they say “FizzBuzz.” Otherwise, they say the number.

In Elixir, we could code this as follows:

control/fizzbuzz.ex
1: defmodule​ FizzBuzz ​do
def​ upto(n) ​when​ n > 0, ​do​: _upto(1, n, [])
5: defp​ _upto(_current, 0, result), ​do​: Enum.reverse result
defp​ _upto(current, left, result) ​do
next_answer =
cond​ ​do
10:  rem(current, 3) == 0 ​and​ rem(current, 5) == 0 ->
"​​FizzBuzz"
rem(current, 3) == 0 ->
"​​Fizz"
rem(current, 5) == 0 ->
15: "​​Buzz"
true ->
current
end
_upto(current+1, left-1, [ next_answer | result ])
20: end
end

The cond starts on line 8. We assign the value of the cond expression to next_answer. Inside the cond, we have four alternatives—the current number is a multiple of 3 and 5, just 3, just 5, or neither. Elixir examines each in turn and returns the value of the expression following the -> for the first true one. The _upto function then recurses to find the next value. Note the use of true -> to handle the case where none of the previous conditions match. This is the equivalent of the else or default stanza of a more traditional case statement.

There’s a minor problem, though. The result list we build always has the most recent value as its head. When we finish, we’ll end up with a list that has the answers in reverse order. That’s why in the anchor case (when left is zero), we reverse the result before returning it. This is a very common pattern. And don’t worry about performance—list reversal is highly optimized.

Let’s try the code in IEx:

 iex>​ c(​"​​fizzbuzz.ex"​)
 [FizzBuzz]
 iex>​ FizzBuzz.upto(20)
 [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz",
 .. 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz"]

In this case, we could do something different and remove the call to reverse. If we process the numbers in reverse order (so we start at n and end at 1), the resulting list will be in the correct order.

control/fizzbuzz1.ex
 defmodule​ FizzBuzz ​do
 
 def​ upto(n) ​when​ n > 0, ​do​: _downto(n, [])
 
 defp​ _downto(0, result), ​do​: result
 defp​ _downto(current, result) ​do
  next_answer =
 cond​ ​do
  rem(current, 3) == 0 ​and​ rem(current, 5) == 0 ->
 "​​FizzBuzz"
  rem(current, 3) == 0 ->
 "​​Fizz"
  rem(current, 5) == 0 ->
 "​​Buzz"
  true ->
  current
 end
  _downto(current-1, [ next_answer | result ])
 end
 end

This code is quite a bit cleaner than the previous version. However, it is also slightly less idiomatic—readers will expect to traverse the numbers in a natural order and reverse the result.

There’s a third option. FizzBuzz transforms a number into a string. We like to code things as transformations, so let’s use Enum.map to transform the range of numbers from 1 to n to the corresponding FizzBuzz words.

control/fizzbuzz2.ex
 defmodule​ FizzBuzz ​do
 def​ upto(n) ​when​ n > 0 ​do
  1..n |> Enum.map(&fizzbuzz/1)
 end
 
 defp​ fizzbuzz(n) ​do
 cond​ ​do
  rem(n, 3) == 0 ​and​ rem(n, 5) == 0 ->
 "​​FizzBuzz"
  rem(n, 3) == 0 ->
 "​​Fizz"
  rem(n, 5) == 0 ->
 "​​Buzz"
  true ->
  n
 end
 end
 end

This section is intended to show you how cond works, but you’ll often find that it’s better not to use it, and instead to take advantage of pattern matching in function calls. The choice is yours.

control/fizzbuzz3.ex
 defmodule​ FizzBuzz ​do
 def​ upto(n) ​when​ n > 0, ​do​: 1..n |> Enum.map(&fizzbuzz/1)
 
 defp​ fizzbuzz(n), ​do​: _fizzword(n, rem(n, 3), rem(n, 5))
 
 defp​ _fizzword(_n, 0, 0), ​do​: ​"​​FizzBuzz"
 defp​ _fizzword(_n, 0, _), ​do​: ​"​​Fizz"
 defp​ _fizzword(_n, _, 0), ​do​: ​"​​Buzz"
 defp​ _fizzword( n, _, _), ​do​: n
 end