Elixir - First steps

Intro

The majority of the code I’ve written over the years has involved C#, Swift, Objective-C or Javascript. Along the way I’ve been reading quite a lot about, as well as dabbled in F#. Heck, I even went to a Clojure session at Devvox Belgium 2015.

Back in January I attended NDC London. Long story short, I ended up in Rob Conery’s session “Three and a half ways Elixir changed me (and other hyperbole)”. The session was interesting but it wasn’t until this week I started looking more closely at the language.

Writing my first Elixir code

The beginner part of the String Calculator kata seemed like a suitable starting point.

Creating the project

Mix is used, among other things, to create, compile and test the code, so I ran the following command:

$ mix new string_calculator

This created the directory string_calculator as well as the following files inside it:

* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/string_calculator.ex
* creating test
* creating test/test_helper.exs
* creating test/string_calculator_test.exs

Writing some code

I opened test/string_calculator_test.exs and wrote my first test:

test "empty string returns 0" do
  assert StringCalculator.add("") == 0
end

The simplest implementation I could think of was:

def add(numbers) do
  0
end

The test(s) are executed by running mix test from the command line.

On to the second test:

test "handles one number" do
  assert StringCalculator.add("1") == 1
end

Elixir functions are identified by name/arity (number of arguments) and pattern matching is done based on the passed arguments, when attempting to find a suitable function to invoke.

I also remembered reading about named functions supporting both do: and do/end block syntax here.

Based on this, my implementation ended up like this:

def add(""), do: 0

def add(numbers) do
  String.to_integer(numbers) 
end

This was confusing and awesome at the same time, considering the programming languages I’m used to.

Let that sink in and let’s continue with the third test:

test "handles two numbers" do
  assert StringCalculator.add("1,2") == 3
end

I’ve been using LINQ as well as lambda expressions extensively in C# over the years, so the anonymous functions felt natural to me. Since I’ve also dabbled in F#, I quickly remembered the pipe operator in Elixir.

This was my first attempt:

def add(numbers) do
  numbers
  |> String.split(",")
  |> Enum.map(fn(x) -> String.to_integer(x) end)
  |> Enum.reduce(fn(x, acc) -> x + acc end)
end

Not too bad, but I had a feeling that the map function could be removed. Quite right, there’s reduce/3 which makes it possible to set the initial value of the accumulator:

def add(numbers) do
  numbers
  |> String.split(",")
  |> Enum.reduce(0, fn(x, acc) -> String.to_integer(x) + acc end)
end

For reference, this is the complete implementation so far:

defmodule StringCalculator do
  def add(""), do: 0

  def add(numbers) do
    numbers
    |> String.split(",")
    |> Enum.reduce(0, fn(x, acc) -> String.to_integer(x) + acc end)
  end
end

On to the next requirement:

test "handles more than two numbers" do
  assert StringCalculator.add("1,2,3,4,5") == 15
end

The implementation already handled this requirement, so I continued with the next one:

test "handles newlines between numbers" do
  assert StringCalculator.add("1\n2,3") == 6
end

I looked at the documentation for String.split/1 and found this:

The pattern can be a string, a list of strings or a regular expression.

The implementation required a small change to make the new test pass:

def add(numbers) do
  numbers
  |> String.split([",", "\n"])
  |> Enum.reduce(0, fn(x, acc) -> String.to_integer(x) + acc end)
end

Moving on to the next requirement:

test "handles custom delimiters" do
  assert StringCalculator.add("//;\n1;2") == 3
end

This is what I came up with, excluding the function that handles empty strings:

def add("//" <> rest) do
  [delimiter, numbers] = String.split(rest, "\n")
  add(numbers, [delimiter])
end

def add(numbers) do
  add(numbers, [",", "\n"])
end

defp add(numbers, delimiters) do
  numbers
  |> String.split(delimiters)
  |> Enum.reduce(0, fn(x, acc) -> String.to_integer(x) + acc end)
end

I knew string concatenation was done with <>, but this Stack Overflow answer made me realize I could use it like in the first function above.

I also made the last function private by using defp instead of def. There should be no add/2 function available to the consumer.

Another thing to note is that the order of the functions matter, when it’s looking for functions to match. So if we for instance swap the order of the first two functions above, the code will break. This is because add(numbers) will match all non-empty strings that are passed as an argument.

On to the final test. First I had to figure out how to test for errors. In the docs I found assert_raise/3:

test "throws error when passed negative numbers" do
  assert_raise ArgumentError, "-1, -3", fn ->
    StringCalculator.add("-1,2,-3")
  end
end

My initial attempt:

defp add(numbers, delimiters) do
  numbers
  |> String.split(delimiters)
  |> check_for_negatives
  |> Enum.reduce(0, fn(x, acc) -> String.to_integer(x) + acc end)
end

defp check_for_negatives(numbers) do
  negatives = Enum.filter(numbers, fn(x) -> String.to_integer(x) < 0 end)
  if length(negatives) > 0, do: raise ArgumentError, message: Enum.join(negatives, ", ") 
  numbers
end

It converted the strings to integers twice, which I wasn’t happy with, so I ended up with this:

defp add(numbers, delimiters) do
  numbers
  |> String.split(delimiters)
  |> Enum.map(fn(x) -> String.to_integer(x) end)
  |> check_for_negatives
  |> Enum.reduce(0, fn(x, acc) -> x + acc end)
end

defp check_for_negatives(numbers) do
  negatives = Enum.filter(numbers, fn(x) -> x < 0 end)
  if length(negatives) > 0, do: raise ArgumentError, message: Enum.join(negatives, ", ")
  numbers
end

I also remembered reading about partial application along the way, which I’d seen in F#.

Elixir supports partial application of functions which can be used to define anonymous functions in a concise way

I decided to give it a go and my string calculator ended up like this:

defmodule StringCalculator do
  def add(""), do: 0

  def add("//" <> rest) do
    [delimiter, numbers] = String.split(rest, "\n")
    add(numbers, [delimiter])
  end

  def add(numbers) do
    add(numbers, [",", "\n"])
  end

  defp add(numbers, delimiters) do
    numbers
    |> String.split(delimiters)
    |> Enum.map(&String.to_integer(&1))
    |> check_for_negatives
    |> Enum.reduce(0, &(&1 + &2))
  end

  defp check_for_negatives(numbers) do
    negatives = Enum.filter(numbers, &(&1 < 0))
    if length(negatives) > 0, do: raise ArgumentError, message: Enum.join(negatives, ", ")
    numbers
  end
end

Looking forward to learning more about Elixir!

Source code

The source code can be found here.