skip to main content

Erlang notes

Notes from study of LYSE (Learn you some Erlang)

What is Erlang?

Erlang is a general purpose programming language and runtime environment, with built-in support for concurrency, distribution and fault tolerance.

Installing Erlang

To install Erlang, run the following command at the terminal.

$ sudo aptitude install erlang erlang-doc erlang-manpages erlang-examples

erlang is the official package that contains the Erlang runtime, applications and the source code for the interpreter and associated tools.

erlang-manpages contain the man page style documentation for the language and libraries.

erlang-doc contains the official Erlang documentation.

erlang-examples contains examples for the language and libraries.

Erlang Shell

Erlang comes with a built-in REPL, called the Erlang shell. It can be invoked by typing erl at the command line.

First things first

All commands must be terminated with a dot (or a period or a full stop) before pressing the Enter key. The dot lets the interpreter know that the command that we want executed is typed and is ready to be executed.

Knowing how to access help is one of the first things to learn when starting out with an interpreter, or for that matter, any command line utility. The aptly named help(). command gives us some useful things to do initially.

The next most obvious thing is to know how to quit. There are two ways:

  1. q(). will get you out of the shell under normal circumstances.
  2. Ctrl-G This is useful for quitting the shell, apart from other things, when the shell freezes from some long running code. Pressing this shortcut will bring up a prompt. Entering q here will get you of the shell.

Trying out some Erlang.

If you have some experience with any REPL, you will know that REPLs also act as calculators for basic arithmetic.

Let us see calculator in action:

$ erl
Erlang R15B01 (erts-5.9.1) [source] [smp:2:2] [async-threads:0] [kernel-poll:false]

Eshell V5.9.1  (abort with ^G)
1> 2+2
1>
1> .
4
2> 2*8*16*144*2304.
36864

Erlang shell generates a line number for each new command. The first command of 2+2 was not terminated (with a dot) until the third line, so the line number 1 was retained until it encountered the dot. In the next line, we tried a large multiplication and got the result back, which is pretty cool.

Let us try assigning the sum of two numbers to a variable.

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

What is this error?

In Erlang, variables start with a capital letter. So the variable sum must really be Sum to function as a variable. But, the error does not indicate that, does it? Yes, it does not, but it will be understood a little later why it says something about matching the right hand side value.

4> Sum = 2+2.
4
5> Sum.
4

Ah, there were go! We have now assigned the sum of 2+2 to the variable Sum successfully and also verified the value stored in Sum in the preceding line (line 5).

6> Product = 3*12.
36
7> Product = 6*6.
36
8> Product = 7*5.
** exception error: no match of right hand side value 35

This error again? So what went wrong now? It is not a helpful error yet, but let us see what we have done here. We have assigned the product of 3 and 12 to Product variable. Then we assigned the product of two sixes to it and it went through fine.

Now, actually what happened in the line 7 was, the product of two sixes, which equals 36, was evaluated first and then matched the value already bound to the variable Product. The original bound value 36 and later evaluated value of 36 both matched. If both the sides of an equation matches, Erlang shell just prints the value again without outputting anything else. If Product variable was unbound in line 7, it would have been assigned the value of 36 freshly. Since it was already a bound variable in line 7, the shell tried to match the left and right sides of the equation and was successful. If the match was not successful, then it would have thrown an error.

In line 8, we tried to assign the product of 7 and 5 to Product and shell says there is not match to the value of 35. This is because of a concept called Pattern Matching in Erlang. The equal to operator also works as a pattern matching operator. Pattern matching will be explored in more detail in the subsequent posts.

9> Quotient = 5/2.
2.5
10> Quotient2 = 5 div 2.
2
11> Reminder = 5 rem 2.
1
12> 16#125.
293
13> q().

The / operator applied on two integers returned a float, which is as intended. Erlang does not distinguish between integers and floats for regular operations. There is also a div operator that lets us deal with integer division and rem operator to act as modulo operator.

16#125 = 293 What is this operation? What is the # operator? This is not an operation, rather a notation to represent numbers in different bases. Here we have expressed the value of the number 125 in base 16. It equals 293.

If we tried 16#FF., the result would have been 255, and so on.

Atoms

In Erlang, atoms are stand-alone non-numerical constants, similar to enumeration types (enums) in C# or Java. In C#, we can have constants like this:

byte connectionError = 0,
byte success = 1,
byte invalidLoginOrPassword = 2

or wrapped inside an enumeration type:

public enum LoginStatus : byte
{
    ConnectionError = 0,
    Success = 1,
    InvalidLoginOrPassword = 2
}

However, there is an important distinction between an enumeration type and an atom. Atoms are just atoms. That is where the stand-alone part comes in. They do not equal a numerical status code or an execution status and they can be used as they are.

The atom connectionError is just what it means without it having to take on any value. It itself is a meaningful entity.

And you could use these directly in code, which clearly results in clearer code:

if login_status in (connectionError,invalidLoginOrPassword)
    warn_caller()
else //
    proceed()

The preceding snippet admittedly looks like Python, as we are yet to study conditionals in Erlang. Now that we have a hint of what purpose atoms can serve, let us understand how to create valid atoms.

1> hello.
hello
2> Greeting=hello_world.
hello_world
3> Greeting1=hello.
hello
4> MyEmail=myname@somehost.com.
'myname@somehost.com'

It is interesting to know that an entire email address can be a valid atom.

Boolean algebra and comparison operators

1> true and false.
false
2> true or false.
true
3> true xor false.
true

This is pretty much how one would expect boolean operators to work.

To test equality:

1> 6 == 6.
true
2> 6 == 6.0.
true
3> 6 =:= 6.
true
4> 6 =:= 6.0.
false

The =.= operator seems like the identity operator in Javascript. Both sides of the comparison should be of same type for the comparison to succeed.

5> 1 =/= 1.
false
6> 1 =/= 1.0.
true
7> 1 /= 1.
false
8> 1 /= 1.0.
false

The =/= operator tests for inequality, but similar to the =:= operator, it also needs both numbers to be of same type.

6> 1 > 2.
false
7> 1 >= 2.
false
8> 2 >= 1.
true
9> 2 <= 1.
* 1: syntax error before: '<='

In Erlang, syntax for the less than or equal to operator is =< instead of <=. So, the following would work.

9> 2 =< 1.
false

This operator appears to say equal to or less than which is pedantically similar to the phrase less than or equal to.

Tuples

Tuples are useful to gather some items to define a structure and generally contain fixed number of elements.

Let's say we want to represent some information about three levels of employees.

Lvl3Employee = {employee,
            {base_id,301},
            {title, 'senior consultant'},
            {base_salary,100.1}}.

Lvl2Employee = {employee,
            {base_id,201},
            {title, 'consultant'},
            {base_salary,70.1}}.

Lvl1Employee = {employee,
            {base_id,101},
            {title, 'associate consultant'},
            {base_salary,30.1}}.

A tuple can contain nested tuples and each tuple can contain any type of data. The tuple Employee contains multiple nested tuples, each of which contain elements of type atom, integer and float.

Each tuple consists of an atom employee as its first element. This is just a convenient way to identify the tuple as holding employee information, and it is not a strict requirement.

Let's say there is some logic in our employee management system, that when creating a new employee, checks to see what level the new hire is and performs some more related operations.

1> {employee,{_,BaseId},{_,Title},{_,BaseSalary}} = Lvl2Employee.
{employee,{base_id,201},
          {title,consultant},
          {base_salary,70.1}}

Now we see some magic unfold:

2> BaseId.
201
3> Title.
consultant
4> BaseSalary.
70.1

There are two things happening here.

  1. Unpacking
  2. Pattern matching.

We created a pattern {employee,{_,BaseId},{_,Title},{_,BaseSalary}} and equated it to a Lvl2Employee.

This pattern matches exactly with the form of the tuples Lvl1Employee, Lvl2Employee and Lvl3Employee.

Because it matched, the elements at respective positions are assigned to their matching elements - 201 with BaseId, consultant with Title and 70.1 with BaseSalary. This is called unpacking of values.

As you might have already guessed, pattern matching is taking place while the values were being unpacked.

Since our pattern matched the tuple and the variables BaseId, Title and BaseSalary were unbound, the values were unpacked and assigned neatly and accordingly. If the pattern did not match or variables were already bound, the match would have failed.

The _ is an anonymous variable which can be used and thrown away.

Lists

Lists are also a way to store a variable number of elements. Lists too can contain any type of elements and are generally considered more flexible. In fact, like most functional programming languages, lists are used extensively in Erlang.

An example list:

1> ShoppingList = [coupons,{bread,2},{jam,1},{fruits,10}].
[coupons,{bread,2},{jam,1},{fruits,10}]
2> lists:nth(2,ShoppingList).
{bread,2}

To access the second element of the list, we used the built-in function nth of the lists module, like this: lists:nth(2,ShoppingList).

Some list operations:

1> [1,2,3] ++ [4,5].
[1,2,3,4,5]
2> [1,2,3,4,5] -- [1,3].
[2,4,5]
3> [1,2] -- [1,2].
[]
4> L = [1,2,3,4,5].
[1,2,3,4,5]

++ operator is used to concatenate two lists, while -- is used to decatenate them. The line 3 produced an empty list.

5> hd(L).
1
6> tl(L).
[2,3,4,5]
7> length(L).
5
8> length([1]).
1
9> length([1,2,3]).
3
10> Tail = tl(L).
[2,3,4,5]
11> Tail.
[2,3,4,5]

hd is a built-in function which retrieves the first element of any list and tl retrieves all the elements of the list except the first element. So, the result of hd(List) is a single number or atom, or a list in case the first element itself is a list, whereas the result of tl(List) is always a list. length is a built-in function which counts the number of elements in a list.

12> Orig_List = [1|Tail].
[1,2,3,4,5]
13> length(Orig_List).
5
14> length([1|2]).
** exception error: bad argument
     in function  length/1
        called as length([1|2])
15> length([1|[2]]).
2

In line 12, we combined the number 1 and another list Tail with a | operator to form another list called Orig_List. The | operator is called a constructor. The expression in line 14 failed because the list [1|2] is not a list of proper form. To be considered proper, a list should be in the form:

16> [1 | []].
[1]
17> [2 | [1 | []]].
[2,1]

List comprehensions

List comprehensions are a way to extract and transform lists to construct other lists. Let us look at a list comprehension:

1> [X*X || X <- [1,1,2,3,5,8,13,21], X rem 2 =/= 0].
[1,1,9,25,169,441]

The expression in line 1 is a list comprehension which operates on the list [1,1,2,3,5,8,13,21]. It generates a new list of numbers which are squares of odd numbers from the original list. The book has a great example that shows how powerful list comprehensions are for real world usage. Borrowed from the book, this list defines menu items and their associated prices.

2> RestaurantMenu = [{steak, 5.99}, {beer, 3.99}, {poutine, 3.50}, {kitten, 20.99}, {water, 0.00}].
[{steak,5.99},
 {beer,3.99},
 {poutine,3.5},
 {kitten,20.99},
 {water,0.0}]

To retrieve a list of items whose price falls between 3 and 10, and apply a tax of 7%, we write the following list comprehension:

3> [{Item, Price*1.07} || {Item, Price} <- RestaurantMenu, Price >= 3, Price =< 10].
[{steak,6.409300000000001},{beer,4.2693},{poutine,3.745}]

Another example with a to do list:

4> ItemsToBuy = [{razor,asap}, {black_board,soon}, {zoom_lens,want}, {laptop_screen,soon}, {running_shoes,want},{tripod,want}, {lens_cleaner,soon}, {groceries,asap}].
[{razor,asap},
 {black_board,soon},
 {zoom_lens,want},
 {laptop_screen,soon},
 {running_shoes,want},
 {tripod,want},
 {lens_cleaner,soon},
 {groceries,asap}]

To retrieve items based on their urgency, we write the following list comprehensions.

5> BuyAsap  = [ Item || {Item, asap} <- Buy].
[razor,groceries]

6> BuySoon  = [ L || {L, soon} <- Buy].
[black_board,laptop_screen,lens_cleaner]

7> BuyLater = [ L || {L, want} <- Buy].
[zoom_lens,running_shoes,tripod]

Let us build some lists from two lists A = [1,3,7,15,31] and B = [1,3,9,27,81].

8> A = [1,3,7,15,31].
[1,3,7,15,31]

9> B = [1,3,9,27,81].
[1,3,9,27,81]

10> C = [X+Y || X <- A, Y <- B, X rem 3 =:= 0, Y rem 3 =:= 0].
[6,12,30,84,18,24,42,96]

11> D = [X*Y || X <- A, Y <- B, X rem 3 =:= 0, Y rem 3 =:= 0].
[9,27,81,243,45,135,405,1215]

12> E = [X*Y || X <- A, Y <- B, X rem 3 =:= 0, Y rem 2 =:= 0].
[]

13> F = [X*Y || X <- A, Y <- B, X rem 3 =/= 0, Y rem 2 =/= 0].
[1,3,9,27,81,7,21,63,189,567,31,93,279,837,2511]

Line 12 produced an empty list because there were no matches for the second condition that states elements of B should be even numbers.

Line 13 produces a big list from the conditions: elements of A must not be divisible by 3 and elements of B must be odd, in this fashion:

1*1,1*3,1*9,1*27,1*81,7*1,7*3,7*9 and so on.

Some more examples:

14> [{X,Y} || X <- [1,2,3,4], Y <- [a,b,c]].
[{1,a},
 {1,b},
 {1,c},
 {2,a},
 {2,b},
 {2,c},
 {3,a},
 {3,b},
 {3,c},
 {4,a},
 {4,b},
 {4,c}]

15> [{X,Y,Z}||X<-[a,b],Y<-[1,2,3],Z<-[c,d]].
[{a,1,c},
 {a,1,d},
 {a,2,c},
 {a,2,d},
 {a,3,c},
 {a,3,d},
 {b,1,c},
 {b,1,d},
 {b,2,c},
 {b,2,d},
 {b,3,c},
 {b,3,d}]

Pythagorean triples can be written in C# as follows:

int limit;
bool isdigit = int.TryParse(Console.ReadLine(), out limit);

if(isdigit)
{
    for(int a = 1; a < limit; a++)
    {
        for(int b = a; b < limit; b++)
        {
            for(int c = b; c <= limit; c++)
            {
                if((a*a+b*b) == (c*c))
                {
                    Console.WriteLine("{0}, {1}, {2}",a,b,c);
                }
            }
        }
    }

Same program with the help of LINQ:

int limit;
bool isdigit = int.TryParse(Console.ReadLine(), out limit);

if(isdigit)
{
    var ts =
      from a in Enumerable.Range(1, 20)
      from b in Enumerable.Range(a, 21 - a)
      from c in Enumerable.Range(b, 21 - b)
      where a * a + b * b == c * c
      select new { a, b, c };

        foreach (var t in ts)
            System.Console.WriteLine("{0}, {1}, {2}", t.a, t.b, t.c);
}
C# output:

3, 4, 5
5, 12, 13
6, 8, 10
8, 15, 17
9, 12, 15
12, 16, 20

In Erlang:

1> [[X,Y,Z]||X<-lists:seq(1,N),Y<-lists:seq(X,N),Z<-lists:seq(Y,N),X*X+Y*Y==Z*Z].
[[3,4,5],[5,12,13],[6,8,10],[8,15,17],[9,12,15],[12,16,20]]

In just a one liner, we have achieved what the above C# program does.

Modules

In Erlang, functions and other units of code can be stored in a file and reused. These files are called Modules and have an extension of .erl Here is a simple module copied from the book.

-module(useless).
-export([greet_and_add_two/1]).

greet() ->
 io:format("Hello!~n").

add(X,Y) ->
 X+Y.

%% only this function is exposed.
greet_and_add_two(X) ->
 greet(),
 add(X,2).

The first line is module declaration which is useful in identifying the current module. The next line is useful for specifying what parts of this module are exposed. These first two lines of code are called Attributes. Attributes define useful information related to the current module, which are available at the compile time.

Let us compile this useless module so that it can be a bit useful. To compile a module from inside the Erlang shell, we can use the function c, which is an alias for compile. Of course, we need to start the shell from where the file exists.

1> c(useless).
useless.erl:2: bad export declaration
useless.erl:4: Warning: function greet/0 is unused
useless.erl:7: Warning: function add/2 is unused
useless.erl:10: Warning: function greet_and_add_two/1 is unused
error

What is this error? When I first created the useless module, the export declaration was wrong. I left out the square brackets in that line and that caused the above error.

2> c(useless).
{ok,useless}

Now the compilation is successful.

3> useless:greet().
** exception error: undefined function useless:greet/0
4> useless:add(2,3).
** exception error: undefined function useless:add/2

Here I tried to import the greet and add methods of the useless module, but since they were not exposed, these two modules were considered nonexistent. Let's try with the function that is exposed.

5> useless:greet_and_add_two(4).
Hello!
6

It worked as expected. It greeted us and printed the sum of 4 and 2. The module greet_and_add_two is pretty long to type. Erlang shell offers some kind of completion for modules and functions. I typed use and pressed the tab key and it showed me a list of modules, that start with the word 'use', available for me.

6> use
useless     user_drv    user_sup

Since I am already in the same path where the useless.erl exists and is compiled just before, it even listed the useless module. Then I typed 'l' and pressed tab key again and it completed the rest of the module name and added a ':' symbol at the end. I pressed tab key once again, it showed its available functions.

6> useless:
greet_and_add_two/1  module_info/0        module_info/1

There are two functions module_info/0 and module_info/1 which seem to be built-in functions for additional information on the module. I tried the module_info/0 since it has no parameters to specify.

6> useless:module_info().
[{exports,[{greet_and_add_two,1},
           {module_info,0},
           {module_info,1}]},
 {imports,[]},
 {attributes,[{vsn,[59091617840888167979942022330653660183]}]},
 {compile,[{options,[]},
           {version,"4.8.1"},
           {time,{2013,3,24,12,10,31}},
           {source,"/home/ab/repos/animesh/gym/erlang/lyse/useless.erl"}]}]

The module_info/0 function here displays a list of attributes about the module including what other modules it may be importing, compiler version, physical location of the file and others.

Here is another simple module that contains functions for most basic mathematical operations.

-module(math).
-export([addn/2,mult/2,subt/2,divn/2]).

addn(A,B) -> A + B.
mult(A,B) -> A * B.
subt(A,B) -> A - B.
divn(A,B) -> A / B.

Compiling the module first:

1> c(math).
{ok,math}
2> math:
acos/1         acosh/1        addn/2         asin/1         asinh/1
atan/1         atan2/2        atanh/1        cos/1          cosh/1
divn/2         erf/1          erfc/1         exp/1          log/1
log10/1        module_info/0  module_info/1  mult/2         pow/2
sin/1          sinh/1         sqrt/1         subt/2         tan/1
tanh/1

Trying the auto completion on the freshly compiled math module revealed a host of other mathematical operations along with my addn, subt, mult, divn functions. This meant there was a built-in math module, but it still compiled my functions and displayed them along with the built-in operations. I think that's pretty neat.

Let us see this in practice.

2> math:addn(29,52).
81
3> math:subt(29,52).
-23
4> math:mult(29,52).
1508
5> math:divn(29,52).
0.5576923076923077

There might be situations where we would want to export all the functions in a module even though some functions are explicitly exported using the -export attribute. To export all functions, we have to use the export_all compiler flag like this:

1> c(useless,[-export_all]).

There are also other compiler flags such as:

  • debug_info This will allow debug information to be embedded in the compiled module, which will be helpful for debuggers and other eco system tools.
  • report_errors/report_warnings This is helpful in reporting errors and warnings as they occur.
  • warnings_as_errors This lets the compiler treat warnings as errors.
  • verbose Emits verbose compilation information.
  • {outdir, Dir} This specifies where the compiled modules should go to.

Multiple compiler flags should be specified as follows:

1> c(useless,[-export_all, debug_info]).

or specify them inside the -compile attribute in the module itself:

-module(useless).
-compile([-export_all, debug_info,warnings_as_errors])