Some fun programming over the weekend, reading a little bit about Elixir, a very beautiful and succinct language. I hope, Elixir, someday we will meet for an exciting project together.

The Basics

Starting the REPL

>iex

Atoms are type in Elixir for which their value as string is also their representation in code. E.g. :test has the string value of test.

All atoms are true except :false and :nil. Operator || will return the first true value. For example, the expression false || :helloworld || true will return :helloworld. Comparing atoms is fast and space efficient. They are similar to comparing enum values in other programming languages.

All strings are UTF encoded. String.length() - length of the string, <> - concatenation operator. String interpolation is built in, with the syntax "Hello #{name}"

Tuples are defined with the syntax t = {:ok, "Hello world"}. Items in a tuple are accessed with elem(t, 0). To change a value in a tuple one can use put_elem(t, 1, "Hello Alexandru"), but this will create a new tuple, as the data is immutable. Elixir supports deconstructing tuples with the syntax {returncode, message} = t.

Lists are composed of heads and tails. l = [1, 2, 3] and then hd(l) will return 1 while tl(l) will return [2, 3]. Deconstructing works with lists as well [h | t] = l.

When counting the elements in a data structure, Elixir also abides by a simple rule: the function is named size if the operation is in constant time (i.e. the value is pre-calculated) or length if the operation is linear (i.e. calculating the length gets slower as the input grows). As a mnemonic, both “length” and “linear” start with “l”. (Elixir Tutorial)

Keyword lists are defined as l = [{:k, :v}] or using the shorthand notation l = [k: :v]. Elements can be accesses as l[:k].

Maps are defined as follows: m = %{:first_key => "this is the value"}. Keys and values can be anything and, if they are atoms, they can be accessed with shortcut notation m.first_key.

Modules are defined as follows:

defmodule ModuleHello do
    def say_hello do
        IO.puts "Hello"
    end
end

and are reloaded in the REPL with r(ModuleHello).

Functions and pattern matching

defmodule PatternMatching do

    def first([]) do: nill
    def first( [head | _]) do: head

end

Invoked as PatternMatching.first([]) or PatternMatching.first([1, 2, 3])

An alternative way

defmodule MyCalendar do

    def is_leap_year(yr) when rem(year, 400) do: true
    def is_leap_year(yr) when rem(year, 100) do: false
    def is_leap_year(yr) when rem(year, 4) do: true
    def is_leap_year(yr) do: false

end

Elixir also supports default parameters using the val \\ default_val syntax, private functions, defined with defp instead of def or discarding parameters which are not used using the _ placeholder.

Function address is taken by using the & operator, for instance when sent as a parameter to another function. Anonymous functions are also supported, e.g. Enum.map([1, 2, 3, 4], fn(n) -> n * n end). Another shorthand notation is the capture-style anonymous function, specified as Enum.reduce([1, 2, 3, 4], 0, &(&1 + &2)). When submitted as a parameter, the syntax for invoking a function is f.(params).

The cond block:

defmodule MyCalendar do
    def day_abbr(day) do:
        cond do:
            day == :Monday -> "Mon"
            day == :Tuesday -> "Tue
            true -> "Neither Mon nor Tue"
    end
end

Using pattern matching do execute parts of code:

defmodule MyCalendar do

    def describe_date(date) do:

        case date do:
            {1, _, _} -> "First day of the month!"
            {25, 12, _} -> "Merry Christmas!"
            {25, month, _} -> "Only #{12 - month} months until Christmas"
            {31, 10, _} -> "Happy Halloween!"
            {_, month, _} when month <= 12 -> "Just an average day"
            {_, _, _} -> "Invalid month"
        end
    end
end

Another useful example of case and pattern matching is handling errors from functions:

def read_file(path) do
    case File.read(path) do:
        {:ok, data} -> process_data(data)
        {:error, err} -> IO.puts "Cannot open file"
end

To start observing the Erlang VM type iex> :observer.start()

ASCII code of a letter is given by the ? prefix, e.g. ?a.

Pattern matching

<<"alexandru", x::binary>> = "alexandru gris"
"alexandru " <> gris = "alexandru gris"

Two examples

A slightly more involved example of pattern matching, a binary search tree example:

defmodule Tree do

  def insert({:leaf, nil}, nv) do
    {:leaf, nv}
  end

  def insert({:leaf, v}, nv) when v >= nv do
    {:node, v, {:leaf, nv}, {:leaf, nil}}
  end

  def insert({:leaf, v}, nv) when v < nv do
    {:node, v, {:leaf, nil}, {:leaf, nv}}
  end

  def insert({:node, v, left, right}, nv) when v >= nv do
    {:node, v, insert(left, nv), right}
  end

  def insert({:node, v, left, right}, nv) when v < nv do
    {:node, v, left, insert(right, nv)}
  end

  def to_list({:leaf, nil}) do
    []
  end

  def to_list({:leaf, v}) do
    [v]
  end

  def to_list({:node, v, left, right}) do
    to_list(left) ++ [v] ++ to_list(right)
  end

  def create() do
    {:leaf, nil}
  end

end

Interesting to note how succinct the code is.

Another one, a small vector class which can be used for maths processing. It uses pattern matching, the pipeline operator and function references to keep the code short.

defmodule Vector do

  def op([h1 | t1], [h2 | t2], f) do
    [f.(h1, h2) | op(t1, t2, f)]
  end

  def op([], [], _) do
    []
  end

  def op([h1 | t1], scalar, f) when is_number(scalar) do
     [f.(h1, scalar) | op(t1, scalar, f)]
  end

  def op([], scalar, _) when is_number(scalar) do
    []
  end

  def add(v1, v2) do
    op(v1, v2, &(&1 + &2))
  end

  def add(v) do
    Enum.reduce(v, &add(&1, &2))
  end

  def subtract(v1, v2) do
    op(v1, v2, &(&1 - &2))
  end

  def negate(v) do
    multiply(v, -1)
  end

  def multiply(v1, v2) do
    op(v1, v2, &(&1 * &2))
  end

  def dot(v1, v2) do
    multiply(v1, v2) |> Enum.sum
  end

  def norm(v) do
    dot(v, v) |> :math.sqrt
  end

  def normalize(v) do
    op(v, norm(v), &(&1 / &2))
  end

  def mean(v) do
    multiply(v, 1 / Enum.sum(v))
  end

  def weighed_mean(v, w) do
    multiply(v, w) |> multiply(1 / dot(v, w))
  end

  def sq_distance(v, w) do
    subtract(v, w) |> (fn x -> dot(x, x) end).()
  end

  def distance(v, w) do
    sq_distance(v, w) |> :math.sqrt
  end

end