A whirlwind introduction to Erlang

As I mentioned in my previous post, I recently took the Functional Programming in Erlang course. Being a Scala developer, I was always wondering how different languages apply the Functional Programming paradigm, especially when it comes to the main source of inspiration for Akka.

In this post I will provide a quick intro to Erlang. We will go through:

  • General information about Erlang
  • Basic data types
  • Functions and recursion
  • Modules
  • Pattern Matching

What is Erlang?

Erlang is a general-purpose, concurrent, dynamically-typed, functional programming language.
It provides a garbage-collected runtime via the BEAM virtual machine (BEAM stands for Bogdan/Björn's Erlang Abstract Machine). It was primarily developed by Joe Armstrong, Robert Virding and Mike Williams and released in 1986 as a proprietary language within Ericsson. In 1998 Erlang was open sourced.

Apart from providing a very neat hot swapping mechanism, Erlang is also known for the Open Telecom Platform which is a fault-tolerant and high-available platform.

Installation

Installation is pretty straightforward. You can either download the binaries from the official website or install using a package manager. I used homebrew to install Erlang (brew install erlang) and it worked out perfectly.

First steps

Let's open the Erlang shell and do some basic operations. To open the Erlang shell type erl in the console (for Windows it's preferable to use werl).

1> 2 + 4.
6
2> 5 * 8.4.
42.0

An interesting aspect of Erlang is that all the expressions end with period (.) . That means you can also have multi-line expressions very easily (i.e. Erlang will consider it a single expression unless the expression terminator . is used):

2> "Hello"       
2> ++     
2> " world!"
2> .
"Hello world!"

To exit the console use q(). or init:stop().

Basic data types

Numbers

Like in other languages Erlang has numbers: integers and floats. Apart from standard notation and operations it provides two additional notations:

  • base#value - Erlang allows to specify numbers in different bases (from 2 to 36):
1> 2#1001.
9
2> 16#1E. 
30
3> 36#20.
72
  • $char - ASCII value or unicode code-point of the character:
1> $a.
97
2> $A.
65

Furthermore, Erlang allows scientific notation to be used (e.g. 2.3e-1 which equals to 0.23).

Atoms

Atoms are constants with names. They are self-contained. If you are familiar with Scala they are similar to symbols but, unlike Scala, they are more widely used. Atoms are enclosed in single quotes (') unless they start with a lower case letter:

1> foo.
foo
2> 'foo'.
foo
3> 'Foo'.
'Foo'

Booleans

There is no Boolean type in Erlang. Instead, the true and false atoms are used to represent Boolean values.

Also, Erlang provides the following boolean expressions: not, and, andalso, or, orelse, xor. While most of them are crystal clear, it's worth mentioning that andalso and orelse are the short-circuit versions for and and or, similar to && and || in Scala.

Tuples

Tuples are compound data types with a fixed number of elements. A tuple is enclosed with curly braces:

1> {1, "two", false}.
{1,"two",false}
2> {}.
{}

You may use the first element to describe the meaning of the tuple. For example, in {person, "John", "Doe", 42} the first element would tell us that this tuple contains information about a person. This can be useful when applying pattern matching on tuples.

Note: Erlang has records which are translated to tuples at compile time using the format above (i.e. the first element of the tuple represents the record type as an atom and then the following elements are the field values).

Lists

A list represents a sequence of elements. In Erlang lists are recursive data structures which:

  • can be empty []
  • consist of a head and a tail [Head| Tail]. Head is the first element, while Tail is the list of remaining items.

A list can be represented using the enumeration notation (e.g. [1,2,3,4]), recursive notation ([1| [2| [3| [4| []]]]]) or a mix of both ([1,2| [3| [4| []]]]):

1> [1,2,3,4].
[1,2,3,4]
2> [1|[2|[3|[4|[]]]]].
[1,2,3,4]
3> [1,2|[3|[4|[]]]].
[1,2,3,4]
4> [1, "two", three, [4]].
[1,"two",three,[4]]

The lists module contains functions for list processing. Also, Erlang supports list comprehensions which are similar to those of Haskell and Scala.

Strings

Strings are enclosed in double quotes (") and are shorthand for lists of chars.

"hello" is equivalent to [$h,$e,$l,$l,$o] and [104,101,108,108,111] .

Comparison operators

For the majority of programming languages the comparison operators are the same. Some of them are defined differently in Erlang:

Operator	Description
==      	Equal to
/= 	     Not equal to
=< 	     Less than or equal to
<  	     Less than
>=	      Greater than or equal to
>	       Greater than
=:=	     Exactly equal to
=/=	     Exactly not equal to

Exact equality/inequality (=:= and =/=) are useful when we want to ensure both operands are of the same type:

1> 42 =:= 42.
true
2> 42 == 42.0.
true
3> 42 =:= 42.0.
false
4> 42 =:= 30.
false

Variables

Erlang has an interesting approach when it comes to variables. Let's have a look at the following variable assignment:

1> Hello = "Hello World!".
"Hello World!"

There are a few things to mention about variables:

  • Variables start with an upper case letter.
  • Values are bound to variables through pattern matching (= is the match operator). If the variable is unbound it will be assigned a value if the match succeeds, otherwise, the variable has a value and can be used in any expressions (functions, pattern matching, case expressions etc.).

If we try to bind a variable twice a match error will occur:

2> Hello = 3.             
** exception error: no match of right hand side value 3

Using the match operator we can also perform assignment to multiple variables using tuples or lists:

1> {A, B} = {3, 4}.
{3,4}
2> A.
3
3> B.
4
4> [Head| Tail] = [1, 2, 3, 4]. 
[1,2,3,4]
5> Head.
1
6> Tail.
[2,3,4]
7> [X, Y, Z | Tail1] = [true, "Hello", 42, {2, 4}, 7.4].
[true,"Hello",42,{2,4},7.4]

When using the match operator you might not always need to bind every value to a variable. If a variable which was already bound is not used a compile warning will be generated. This is where the anonymous variable is very handy. It is defined as underscore (_), similarly to Haskell or Scala.

However, in some cases it's still useful to have a meaningful name even though a variable is not used (e.g. when declaring function clauses). Luckily, in Erlang we can achieve this by prepending _ to the variable (e.g. _Foo). Even though we won't use it, Erlang will not generate warnings for a variable that starts with _.

Functions and recursion

Functions play an important role in functional programming languages, and Erlang is no exception. In Erlang functions consist of clauses which are separated by semicolons ; -
except for the last one, which ends with a period (.). Pattern matching and guards are used to determine which clause to invoke:

take(0, _Xs) -> [];
take(_N, []) -> [];
take(N, [X| Xs]) when N > 0 -> [X | take(N-1, Xs)].

This defines the recursive take function which returns the first N elements from a list. Let's analyse the function clauses:

  • If we are taking the first 0 elements (i.e. N = 0) then just return an empty list;
  • If we are taking the first N elements from an empty list then just return an empty list;
  • If we are taking the first N elements (where N > 0) from a non empty list, add the head element to the result list and recursively invoke the function with N-1 (the number of elements left to take) and the tail of the list (take(N-1, Xs))

In order to run functions we need to put them in modules. The next section covers this.

Note: Guards are defined using the when keyword and a condition. The designers of the language decided that the condition should be an expression that will always terminate successfully. That means user-defined functions are not allowed to be used in guards.

It's worth mentioning that Erlang has tail-call optimisation, which will reuse the current stack frame instead of adding a new one to the call stack.

Erlang also has anonymous functions, which are defined in a similar way to named functions. Instead of specifying the function name we use the fun keyword and end the definition with end.:

1> Double = fun(X) -> 2 * X end.
#Fun<erl_eval.6.52032458>
2> Double(21).
42

Defining anonymous functions with multiple clauses is simple too:

3> Abs = fun(X) when X >= 0 -> X;
3> (X) when X < 0 -> -X end.
#Fun<erl_eval.6.52032458>
4> Abs(3).
3
5> Abs(-4).
4
6> Abs(0).
0

Higher-order functions are functions that may return a function and/or take one or more functions as arguments. This is one of the things that makes functional programming so powerful.

One of the most used functions is map, which applies the provided function on every element of the input list:

7> lists:map(Double, [1, 2, 3, 4, 5]).
[2,4,6,8,10]

You can also pass the function directly without binding it to a variable:

lists:map(fun (X) -> X * 2 end, Xs).

Named functions can be passed as parameters as well but with a slightly different notation. They should have the arity specified and follow the fun keyword - otherwise Erlang would interpret these as atoms.

inc(X) -> X + 1.
lists:map(fun inc/1, Xs).

Modules

Functions are organised in modules. A module consists of a sequence of attributes and functions. Let's define a new module and see what it looks like:

foo.erl

-module(foo).
-export([square/1]).

square(A) ->
    A * A.

Let's analyse the structure:

  • The first line defines the module with the name foo: -module(foo).
  • We then use the export attribute for choosing the methods we want to make available to other modules. It takes an array of functions as the parameter (the function arity should be specified as well). In the module defined above we export the square function which has arity of 1.
  • And finally we have the function definitions.

We will use the c(Module) command to compile and load the module:

1> c(foo).
{ok,foo}

Now the module is loaded and we can call the square function. The function has to be called in the fully-qualified form:

2> foo:square(4).
16

Note: When defining a module you can use -import(Module, Functions) to import functions from other modules that can be called the same way as local functions.

Pattern matching

Similar to other functional programming languages, pattern matching in Erlang makes it possible to deconstruct a value and take specific actions based on its structure.

We already saw two ways to perform pattern matching: using the match operator (=) and the function clauses.

In this section we will cover the case expression. The general format of case expressions is:

case Expression of ->
  Pattern1 [when Guard1] -> Body1;
  ...;
  PatternN [when GuardN] -> BodyN
end

Using pattern matching the factorial function looks like this:

fact(N) ->
  case N of
    1 ->
      1;
    N when N > 0 ->
      N * fact(N - 1)
  end.

Wrap-up

In this short compressed post we went through the basics of Erlang syntax and semantics, with a slight focus on the functional programming aspects. In order to have a bigger feel on what makes Erlang so powerful, I would encourage to look at the resources mentioned in the previous post, and then to have a look at the Open Telecom Platform.

Happy learning!