3. Numbers

In Elixir, numbers are divided into two categories: integers and floats. Integers are whole numbers, the type you count on your hand: 3, 0, -1123, etc. Floats are numbers that can contain fractional parts, like 1.333333 or -0.01. They are called "floats" because of the way they are represented in memory. You don’t need to know that, but if you want to learn more you can read up on floating-point arithmetic. To be honest, I had to look it up myself while writing this very paragraph!

In this chapter we will go over the basics of numbers and floats, along with introducing some other pattern matching mechanisms that Elixir has, among other things. Let’s dive in!

3.1. Fibonacci

The Fibonacci numbers are a famous set of numbers that form a sequence, where each number is the sum of the two preceding ones. You may have seen them before:

Listing 1. Fibonacci numbers
0, 1, 1, 2, 3, 5, 8, 13, 21...

It is represented mathematically like so:

Listing 2. Fibonacci sequence
F₀ = 0
F₁ = 1
Fₙ = F₀ + F₁

Which means 2 in the sequence above is the 3rd Fibonacci number, and 0 is the 0th (zeroth).

Let’s write a program that can compute any Fibonacci number for us. We can give it a number, n, like 6 and it will return the corresponding Fibonacci number, 8.

3.1.1. Function pattern matching

Let’s start with the test:

Listing 3. numbers/fibonacci_test.exs
Code.require_file("fibonacci.ex")
ExUnit.start()

defmodule FibonacciTest do
  use ExUnit.Case

  test "calculates 0th number" do
    assert Fibonacci.calculate(0) == 0
  end

  test "calculates 1st number" do
    assert Fibonacci.calculate(1) == 1
  end
end

Here we are just testing the 0th and 1st number, but we’ll add more later. Run the test to ensure that it fails, as we would expect:

** (Code.LoadError) could not load [...]fibonacci.ex
    (elixir 1.12.2) lib/code.ex:1807: Code.find_file/2
    (elixir 1.12.2) lib/code.ex:1254: Code.require_file/2
    fibonacci_test.exs:1: (file)
    (elixir 1.12.2) lib/code.ex:1261: Code.require_file/2

Our fibonacci.ex file doesn’t exist yet, so our test is failing to run. Note that "failing to run" is not the same as "running, then failing". In this case, our tests are not actually running! Let’s create fibonacci.ex and add the minimal code needed to make our fibonacci_test.exs actually run:

Listing 4. numbers/fibonacci.ex
defmodule Fibonacci do
end

With that in place, we can see the familiar ExUnit output:

$ elixir fibonacci_test.exs
warning: Fibonacci.calculate/1 is undefined or private
Found at 2 locations:
  fibonacci_test.exs:8: FibonacciTest."test calculates 0th number"/1
  fibonacci_test.exs:12: FibonacciTest."test calculates 1st number"/1

  1) test calculates 0th number (FibonacciTest)
     fibonacci_test.exs:7
     ** (UndefinedFunctionError) function Fibonacci.calculate/1 is undefined or private
     code: assert Fibonacci.calculate(0) == 0
     stacktrace:
       Fibonacci.calculate(0)
       fibonacci_test.exs:8: (test)

  2) test calculates 1st number (FibonacciTest)
     fibonacci_test.exs:11
     ** (UndefinedFunctionError) function Fibonacci.calculate/1 is undefined or private
     code: assert Fibonacci.calculate(1) == 1
     stacktrace:
       Fibonacci.calculate(1)
       fibonacci_test.exs:12: (test)

Finished in 0.1 seconds (0.1s on load, 0.00s async, 0.01s sync)
2 tests, 2 failures

Randomized with seed 362639

Now let’s write the code needed to make it pass:

Listing 5. numbers/fibonacci.ex
defmodule Fibonacci do
  def calculate(0) do
    0
  end

  def calculate(1) do
    1
  end
end

In Elixir, we can also do "matching" within the function definition itself, which is what we are doing here. When you call calculate/1 with a parameter, Elixir will find the function definition that "matches" the parameter you provide. This works similarly to how case works, which we saw in action in the previous chapter.

So when we call calculate/1 with the value of 1, as we do in our test, Elixir will first look at def calculate(0), see that 1 != 0, and then look at the next definition. The next definition, def calculate(1) does match, and Elixir will in turn execute the do/end block associated with that definition.

Since functions are dispatched in this "matching" manner, we can define the same function multiple times. That is, in addition to defining functions with different arities, like greet/0 and greet/1, we can also define multiple functions with the same arity, like we do here with calculate/1. Functions, just like case, are matched in order from the first definition in the module to the last, too.

If you haven’t already, run the tests to verify that your code passes:

$ elixir fibonacci_test.exs
..

Finished in 0.04 seconds (0.03s on load, 0.00s async, 0.01s sync)
2 tests, 0 failures

Randomized with seed 601683

3.1.2. Recursion

Let’s define the rest of the behavior we want from our calculate/1 function with some more tests:

Listing 6. numbers/fibonacci_test.exs
Code.require_file("fibonacci.ex")
ExUnit.start()

defmodule FibonacciTest do
  use ExUnit.Case

  test "calculates 0th number" do
    assert Fibonacci.calculate(0) == 0
  end

  test "calculates 1st number" do
    assert Fibonacci.calculate(1) == 1
  end

  test "calculate 8th number" do
    assert Fibonacci.calculate(8) == 21
  end
end

Here we add a test for the 8th Fibonacci number, which I got from the sequence at the top of this chapter. With that added, let’s run our tests:

Listing 7. FunctionClauseError
$ elixir fibonacci_test.exs
..

  1) test calculate 8th number (FibonacciTest)
     fibonacci_test.exs:15
     ** (FunctionClauseError) no function clause matching in Fibonacci.calculate/1

     The following arguments were given to Fibonacci.calculate/1:

         # 1
         8

     code: assert Fibonacci.calculate(8) == 21
     stacktrace:
       fibonacci.ex:2: Fibonacci.calculate/1
       fibonacci_test.exs:16: (test)



Finished in 0.09 seconds (0.07s on load, 0.00s async, 0.02s sync)
3 tests, 1 failure

Randomized with seed 30951

Our test fails with a FunctionClauseError. Though we do have a calculate/1 function defined, none of its function clauses match the parameter we passed in. This is because our current calculate/1 only has function clauses for 0 and 1.

Let’s add a third function clause that handles the case for the third or later Fibonacci number. By referencing the formula in Listing 2, we come up with this:

Listing 8. numbers/fibonacci.ex
defmodule Fibonacci do
  def calculate(0) do
    0
  end

  def calculate(1) do
    1
  end

  def calculate(n) do
    calculate(n-1) + calculate(n-2)
  end
end

Our code here looks almost exactly like the formula. If the parameter we get is 0 or 1, we return those respective values, otherwise we recursively compute the value by calculating the prior two Fibonacci numbers and adding them together.

Run the tests to verify the solution works.

3.1.3. Function clause guards

Our Fibonacci module works great. However, there is one problem: other programmers keep on calling it with strings instead of integers! They pass in "5" instead of 5. They say calling it with these "numbers" results in an ArithmeticError in our module. Because the error comes from our module, they insist it is our code that has the bug not theirs!

We did not really expect this sort of usage, so we should guard against it. Our code should be defensive against "bad" inputs, to make it clear to other programmers using our module that the issue is not in our module, but rather in how they are using our module.

Instead of having our code raise an ArithmeticError, we should raise an error that indicates to the programmer that our function is not meant to be used in the way they are using it. That it doesn’t accept the parameters that are passed in.

What sort of error should we raise? How about the FunctionClauseError that we encountered earlier in Listing 7, when we tried to call calculate/1 with a number before we defined that functionality. That makes it clear that our function has no clause for their parameter, which is much more clear!

In Elixir, we can assert an error is raised with the handy assert_raise/2 function. Let’s add a test, using that:

Listing 9. numbers/fibonacci_test.exs
defmodule Fibonacci do
  def calculate(0) do
    0
  end

  def calculate(1) do
    1
  end

  def calculate(n) do
    calculate(n-1) + calculate(n-2)
  end
end

assert_raise/2 takes in an exception and a function to run that should raise that exception when executed. In Elixir, in addition to defining a function in a module with def, we can also define a function within a block with the fn -> shorthand. This is known as an anonymous function, because it has no name. We will cover anonymous functions in much more detail in a later chapter.

Let’s run our test:

$ elixir fibonacci_test.exs
..

  1) test handles strings (FibonacciTest)
     fibonacci_test.exs:19
     Expected exception FunctionClauseError but got ArithmeticError (bad argument in arithmetic expression)
     code: assert_raise FunctionClauseError, fn ->
     stacktrace:
       fibonacci.ex:11: Fibonacci.calculate/1
       fibonacci_test.exs:20: (test)

.

Finished in 0.09 seconds (0.06s on load, 0.00s async, 0.03s sync)
4 tests, 1 failure

Randomized with seed 805110

Great! Here we have added a test that reproduces what those other programmers were telling us, that our code is raising an ArithmeticError. We see that the test fails because our asset_raise was expecting a FunctionClauseError.

In Elixir, we can guard against bad inputs with Guards. Essentially, in a function clause, we can add a guard that tells Elixir what sort of things the function is willing to accept.

There are many types of guards. For us, we want to check that the parameter is a number. So we can use the is_integer one.

Add it to your last function clause like so:

Listing 10. numbers/fibonacci.ex
defmodule Fibonacci do
  def calculate(0) do
    0
  end

  def calculate(1) do
    1
  end

  def calculate(n) when is_integer(n) do
    calculate(n-1) + calculate(n-2)
  end
end

And run the tests again. They all pass! Now next time someone tries to call our calculate/1 function with a string, they will get a FunctionClauseError, making clear to them that our function does not match their parameters.

3.1.4. Just , do: it

This pattern of having a variety of short function definitions to implement a given function is quite common in Elixir. It is not unusual to have even up to ten function clauses for a single function. This has the benefit of skimming the various cases the function could handle quite easy. However, it does often feel more verbose than just having that matching logic within a single function via case or similar.

Luckily, in addition to the traditional do/end deliminated function body block, Elixir also has a "shorthand" way of defining the function body by using just , do: alone. Update your fibonacci.ex file as follows, to see it in action:

Listing 11. numbers/fibonacci.ex
defmodule Fibonacci do
  def calculate(0), do: 0
  def calculate(1), do: 1
  def calculate(n) when is_integer(n), do: calculate(n-1) + calculate(n-2)
end

This shorthand style is only used when the body of the function only has a single line of code. As you see, it compresses the number of lines a function takes up from three to one, in our case. Note that for this , do: style, we have a comma after the closing parenthesis. In the do/end style, there is no such comma.

After refactoring, run the tests again to make sure your refactor did not introduce any regressions.

3.2. FizzBuzz

One of the most common interview questions out there is implementing Fizz buzz. It’s a simple game where we print a range of numbers with the following rules:

  • If the number is divisible by 3, print "Fizz"

  • If the number is divisible by 5, print "Buzz"

  • If the number is divisible by 3 and 5, print "FizzBuzz"

  • Otherwise, print just the number.

Actually, one of my first programming jobs I got in college had this question in the interview. I think that was the first time I ever saw it.

Anyway, let’s implement this in Elixir!

3.2.1. Remainders with rem

As always, start with a test. Create a new file, fizz_buzz_test.exs, and let’s translate the requirements above into code:

Listing 12. numbers/fizz_buzz_test.exs
Code.require_file("fizz_buzz.ex")
ExUnit.start()

defmodule FizzBuzzTest do
  use ExUnit.Case

  test "handles divisible by 3" do
    assert FizzBuzz.solve(3) == "Fizz"
  end

  test "handles divisible 5" do
    assert FizzBuzz.solve(5) == "Buzz"
  end

  test "handles divisible by 3 and 5" do
    assert FizzBuzz.solve(15) == "FizzBuzz"
  end

  test "handles not divisible" do
    assert FizzBuzz.solve(7) == 7
  end
end

Additionally, create a fizz_buzz.ex file that simply defines our module:

defmodule FizzBuz do
end

And let’s run our tests to verify they are failing, as we would expect:

$ elixir fizz_buzz_test.exs
warning: FizzBuzz.solve/1 is undefined or private
Found at 4 locations:
  fizz_buzz_test.exs:8: FizzBuzzTest."test handles divisible by 3"/1
  fizz_buzz_test.exs:12: FizzBuzzTest."test handles divisible 5"/1
  fizz_buzz_test.exs:16: FizzBuzzTest."test handles divisible by 3 and 5"/1
  fizz_buzz_test.exs:20: FizzBuzzTest."test handles not divisible"/1

  1) test handles not divisible (FizzBuzzTest)
     fizz_buzz_test.exs:19
     ** (UndefinedFunctionError) function FizzBuzz.solve/1 is undefined or private
     code: assert FizzBuzz.solve(7) == 7
     stacktrace:
       FizzBuzz.solve(7)
       fizz_buzz_test.exs:20: (test)

  2) test handles divisible by 3 (FizzBuzzTest)
     fizz_buzz_test.exs:7
     ** (UndefinedFunctionError) function FizzBuzz.solve/1 is undefined or private
     code: assert FizzBuzz.solve(3) == "Fizz"
     stacktrace:
       FizzBuzz.solve(3)
       fizz_buzz_test.exs:8: (test)

  3) test handles divisible by 3 and 5 (FizzBuzzTest)
     fizz_buzz_test.exs:15
     ** (UndefinedFunctionError) function FizzBuzz.solve/1 is undefined or private
     code: assert FizzBuzz.solve(15) == "FizzBuzz"
     stacktrace:
       FizzBuzz.solve(15)
       fizz_buzz_test.exs:16: (test)

  4) test handles divisible 5 (FizzBuzzTest)
     fizz_buzz_test.exs:11
     ** (UndefinedFunctionError) function FizzBuzz.solve/1 is undefined or private
     code: assert FizzBuzz.solve(5) == "Buzz"
     stacktrace:
       FizzBuzz.solve(5)
       fizz_buzz_test.exs:12: (test)

Finished in 0.1 seconds (0.1s on load, 0.00s async, 0.00s sync)
4 tests, 4 failures

Randomized with seed 702309

We haven’t implemented solve/1 yet, so these UndefinedFunctionError failures are exactly what we would expect.

In Elixir, we can find whether one number is divisible by another with the built-in rem/2 function. If you have worked with other programming languages, this is the equivalent of the module operator, %. It simply returns the remainder of the division operation. For example rem(10,2) == 0 and rem(10,3) == 1.

Let’s try implementing this with guards and case:

Listing 13. numbers/fizz_buzz.ex
defmodule FizzBuzz do
  def solve(num) do
    case num do
      num when rem(num, 3) == 0 and rem(num, 5) == 0 ->
        "FizzBuzz"
      num when rem(num, 3) == 0 ->
        "Fizz"
      num when rem(num, 5) == 0 ->
        "Buzz"
      not_divisible ->
        not_divisible
    end
  end
end

The same guards we used on our function clauses earlier can also be used on case clauses. Here we also use the and operator to combine two guards for our first clause, to ensure it is divisible by both 3 and 5.

You may be asking: why don’t we use function guard clauses instead, like we did with Fibonacci? There is no particular reason, and using function guard clauses would work just as well. In fact, that is one of the exercises at the end of this chapter.

Run the tests again to verify everything passes.

3.2.2. Conditions with cond

In addition to case and if/else, Elixir has the cond flow control structure.

To review, if evaluates a single expression, and runs the associated block if that expression evaluates to true, otherwise evaluating the else block, if it exists. case uses pattern matching, evaluating the block of code associated with the matching clause.

cond is similar to else if in other programming languages. Instead of working on matching, it works in the same way if does: by evaluating expressions one by one and running the block of code associated with the first expression that evaluates to true.

Let’s refactor our code to use cond, to explore further:

Listing 14. numbers/fizz_buzz.ex
defmodule FizzBuzz do
  def solve(num) do
    cond do
      rem(num, 3) == 0 and rem(num, 5) == 0 ->
        "FizzBuzz"
      rem(num, 3) == 0 ->
        "Fizz"
      rem(num, 5) == 0 ->
        "Buzz"
      true ->
        num
    end
  end
end

As you can see, cond is a bit more succinct in this case. As mentioned, cond is used when we have a variety of expressions to evaluate. It is typically used less frequently that case and pattern matching in general.

Our last condition is simply true, which is always true, so serves as our final catch-all. If a cond construct has no expression that evaluates to true, then a CondClauseError will be raised.

Run the tests to ensure that our little refactor did not introduce any regression, and that the tests still pass.

3.3. Whirlwind operator tour, with tests

In a chapter called "Numbers", we’ve only used the rem and + operators so far! Let’s fix that by doing a bit of a whirlwind tour of all the operators we typically use with numbers. We won’t be creating a module this time, but will instead be expanding and reinforcing our understanding by creating a series of tests that explore each of them.

Create a file called numbers_test.exs, where we will add our tests:

ExUnit.start()

defmodule NumbersTest do
  use ExUnit.Case
end

Let’s get started!

3.3.1. Number operators

In addition to the + operator, Elixir of course also has the - operator. It works just as you would expect it to:

Listing 15. numbers/numbers_test.exs
  test "- operator" do
    assert 1 - 5 == -4
  end

Related to the rem operator is the div operator. div is the integer division operator. It only returns the result rounded towards zero.

Listing 16. numbers/numbers_test.exs
  test "div operator" do
    assert div(10, 2) == 5
    assert div(11, 2) == 5
    assert div(12, 2) == 6
  end

You may want a float result instead. For that you can use the / operator.

Listing 17. numbers/numbers_test.exs
  test "/ operator" do
    assert 10 / 2 == 5.0
    assert 11 / 2 == 5.5
    assert 12 / 2 == 6.0
  end

When any sort of operation uses both an integer and a float, the result is usually a float. We can show this by way of the * operator, which multiplies.

Listing 18. numbers/numbers_test.exs
  test "* operator" do
    assert 1.0 * 5 == 5.0
    assert 50 * 20 == 1000
    assert 2.2 * 4.1 == 9.02
  end

3.3.2. Comparison operators

All of our tests already use at least one comparison operator: ==! It compares the value between the two operands, as we know. It isn’t very strict when comparing between floats and integers though.

Listing 19. numbers/numbers_test.exs
  test "== operator" do
    assert 1.0 == 1
    assert 1.0000000000000001 == 1
  end

The === operator is more strict when comparing between floats and integers.

Listing 20. numbers/numbers_test.exs
  test "=== operator" do
    refute 1.0 === 1
    refute 1.0000000000000001 === 1
    assert 1 === 1
    assert 1.01 === 1.01
  end

Here we use the refute/1 macro. It’s the opposite of assert, and only succeeds if the operation does not return a truthy value.

We also have the != and !== operators, which test that two values do not match. The !== operator, like the === operator, is more strict in its comparison.

Listing 21. numbers/numbers_test.exs
  test "!= and !== operators" do
    refute 1.0 != 1
    assert 1.0 !== 1
  end

Of special interest to numbers is determining whether a number is smaller or larger. We can do this with the < and > operators, respectively.

Listing 22. numbers/numbers_test.exs
  test "> and < operators" do
    assert 1 < 2
    assert 1 < 1.00001
    assert 9001 > 9000
    assert -55 > -60
  end

We can tack on a = to the end of each to also return true if the values are equal.

Listing 23. numbers/numbers_test.exs
  test ">= and <= operators" do
    assert 1 >= 1.0
    assert 1 <= 1.00001
  end

Whew! And with that, we know all the operators we need to know for numbers.

3.4. Conclusion

In this chapter we got to explore more of the basics. You are now equipped to pass two of the most common interview questions: Fibonacci and FizzBuzz! Next up is learning about two of the more unique parts of Elixir: tuples and the = match operator, among many other things. Onward!

3.4.1. What we learned in this chapter

  • Function definitions can utilize pattern matching, just like case.

  • Guards are used for making our match more specific. They can be added to match clauses, like function definitions and case clauses.

  • We can use the shorthand , do: syntax to make our function bodies more concise.

  • rem/2 can be used to calculate the remainder of a division operation.

  • cond can be used to evaluate a series of expressions to determine what block of code to execute. It is similar to "else-if" chains in other programming languages.

  • A whirlwind tour of all the other operators in Elixir!

3.4.2. Exercises

  • Calling Fibonacci.calculate/1 with a negative number like -1 results in the program hitting an infinite loop. Add a functional guard clause to guard against negative numbers. Hint: you can use comparison operators in guard clauses.

  • Convert FizzBuzz.solve/1 to use function clause guard matching instead of cond.

  • Convert Fibonacci.calculate/1 to use cond instead of function clause guard matching.

  • Update FizzBuzz to print "aww" if a number is not divisible by 3 or 5, instead of printing the number. As always, update the test, first!

  • FizzBuzz.solve/1 currently does not guard against non-number parameters. Add a guard clause doing just that.