Paraxial.io Blog

Elixir code style, a brief example

by Michael Lubas

Write a function in Elixir named build_string that takes 4 arguments:

a - An integer
b - An integer
ops - A list of the atoms [:add, :sub, :mul, :div] in any order, max length of 10 
sep - A separator, such as "-" or ","

and returns a string where a and b are calculated, in-order, together according to the list of operations, delimited by the separator value. For example:

> build_string(8, 2, [:add, :sub], ",")   
> "10,6"

> build_string(10, 5, [:div, :sub, :mul, :add], "-")
"2-5-50-15"

Here is an example of bad style:

defmodule Codex do

  # Bad style
  def build_string(a, b, ops, sep) do
    Enum.reduce(ops, "", fn op, acc ->
      acc <> sep <> Integer.to_string(calculate(a, b, op))
    end)
    |> String.trim_leading(sep)
  end

  def calculate(a, b, op) do
    case op do
      :add ->
        a + b
      :sub ->
        a - b
      :mul ->
        a * b
      :div ->
        div(a, b)
    end
  end
end

Focus on the build_string/4 function:

  def build_string(a, b, ops, sep) do
    Enum.reduce(ops, "", fn op, acc ->
      acc <> sep <> Integer.to_string(calculate(a, b, op))
    end)
    |> String.trim_leading(sep)
  end

From the start at Enum.reduce, the accumulator is a blank string, so reduce is creating a string. It is iterating over ops, so the accumulator will be built up in steps:

> build_string(8, 2, [:add, :sub], ",")   
> # inside Enum.reduce
> "" <> "," <> "10"
> ",10"
> ",10" <> "," <> "6"
> ",10,6"

Then there is a call to trim the leading sep, in this case ",". The call to String.trim_leading/2 here is a red flag that there is a special case in the code evaluation, an additional leading separator value that should not have been introduced.

Now consider this version of build_string/4:

  # Good style 
  def build_string(a, b, ops, sep) do
    ops
    |> Enum.map(&calculate(a, b, &1))
    |> Enum.join(sep)
  end

Enum.map conveys that calculate/3 transforms every element in the list, preserving the order. Elixir provides Enum.join, but even if it was not included, writing a function to join all the elements of the list together with a separator value makes this much more clear.

> build_string(8, 2, [:add, :sub], ",")   
> [10, 6] # Produced by Enum.map([:add, :sub], &calculate(8, 2, &1))
> "10,6"  # Produced by Enum.join([10, 6], ",")

This avoids the problem of building up a string where the leading separator needs to be removed. The problem given may seem trivial, and it is. Making your code as easy to read as possible, by avoiding unnecessary cases, is the important point.

Paraxial.io makes blocking bots in Elixir and Phoenix applications easy.

Enter your email to stay up to date.