Getting started with Erlang #2

Vanshdeep Singh
14 min readOct 2, 2015

--

This post follows from my previous post where I gave a brief overview of Erlang and the way one can start learning it ! The previous post covered the following topics:

  • Variable Types: different data types offered by Erlang.
  • Variables: how to declare and use them in Erlang.
  • Module: how create an Erlang module and write functions.
  • Pattern Matching: discussed what it is and how to use it !
  • Functions and Pattern Matching: how pattern matching works in functions.

In this post I will discuss some other important variable types, dive deeper into functions and list some stuff that will come in handy while writing or reading Erlang code. So, let get started !

Variables Types

Previously I discussed basic variables types like strings, number, decimals, lists and tuples which are usually seen in most of the languages. Now I will go a step further and discuss Binaries, Maps and Records.

Binaries: This is a helpful variable type when you want to do bit level manipulation on data. Lets look at some examples to have a better understanding,

MyBinary = <<1, 7, 42>>.

above is how to write a binary. Notice that it is equivalent to writing a list but here we use “<<” and “>>” to encapsulate instead of “[” and “]” . You can remember them as the left shift and right shift operators from python or C.

Another thing to remember is that each of the element in the above binary occupies 1 byte or 8 bits by default i.e. “1” occupies 8 bits, “7” occupies 8 bits and “42” also occupies 8 bits. So the maximum value you can write is 255 (i.e. 2^8 –1). But what if you want to write more than that, following is how you do it,

1> MyBinary2 = <<1:1, 3:7, 2, 42:16>>.
<<131,2,0,42>>

Notice the “42:16” above. Here we declare that “42” will occupy the 16 bits of the space which allows us to write any number upto 65535 (2^16 -1). Further, we specify that “1” will occupy only 1 bit and “3” will occupy 7 bits. Notice that the final representation returned by the shell has “131” , where did it come from ?

The reason is that Erlang interprets the binary in a per byte matter i.e. the first value in the binary returned by the shell represents the value contained in the first 8 bits of the binary. This also explains why we have “0” in the returned binary because 16 bits are interpreted as 2 bytes and “42” fits into the lower half of these 16 bits hence the upper half has a value of “0”. So you can see that one can specify any amount of bits a particular value will occupy.

Binaries are a popular data type among Erlang programmers. There are quite a few reason for this but the one we can discuss right now is that it’s easier to construct and destruct bit level data in binaries. The examples above showed how to construct binary and now we will see how to destruct it.

Recall from the previous post, we used pattern matching to extract data from lists and tuples. Here also we use pattern matching to extract data from the binary,

2> <<A:1, B:7, C, D:16>> = MyBinary2.%% A -> 1, B -> 3, C -> 2, D -> 42

Notice here while destructing we specify the number of bits to be stored in each variable. So “A:1” will store the first bit in “A”, “B:7” will store the next 7 bits in “B”, “C” will have the next 8 bits (because default number of bits if not specified is 8) and “D:16” will have the next 16 bits.

In the above example you knew the number of bits (i.e 1, 7, 8, 16) to be stored in each of the variable but what if you don’t know that or maybe the destructing information is packed in the binary itself. Fear not ! I have a solution for you, let's say you wanted to extract only the first two values of “MyBinary2”,

<<E:1, F:7, G/binary>> = MyBinary2% E = 1
% F = 3
% G = <<17,0,42>>
<<H, I:16>> = G% H = 2
% I = 42
  • In the first line we pattern match the values from “MyBinary2”. In the match we specify how many bits will be stored in particular variable. So “E:1” will store the first bit, “F:7” will store the next 7 bits.
  • Lastly we match the remaining binary into G using “/binary”. It is worthwhile to mention here that you need make sure that the remaining binary has its size in multiples of 8 else the pattern match will fail i.e.
1> <<E:1, F/binary>> = <<1:1, 3:7, 2:8, 42:16>>.
** exception error: no match of right hand side value <<131,2,0,42>>

We will see how to get around this later.

  • Also “/binary” should only be used for the last term in the pattern match else the pattern match won’t work,
1> <<E:1, F/binary, G/binary>> = MyBinary2.
* 1: a binary field without size is only allowed at the end of a binary pattern

You see, the error message says that a field without size is only allowed at the end !

Bitstring

A bit string is a sequence of bits of any length, this separates it from a binary which is a sequence of bits where the number of bits is evenly divisible by eight.

For all practical purposes following is how you write a bitstring,

1> MyFirstBinaryString  = <<"this a binary string">>.
<<"this a binary string">>
2> BinaryString = <<"erlang", 42, "love">>.
<<"erlang*love">>
3> <<"erlang", Universe, F/bitstring>> = BinaryString.
% Universe = 42
% F = "love"
  • In the first line we write a binary string. Recall how you write are string, just add “<<” and “>>” to make it a binary string.
  • In the next line we construct a binary string with mixed values (integer and string). Notice that the shell returns <<erlang*love”>>, here 42 has been converted to its ascii value. If you used a number that doesn't have any ascii representation then the whole binary string would be represented as number where the characters will be written as their ascii codes, for eg.
1> <<"erlang", 2700, "love">>.
<<101,114,108,97,110,103,140,108,111,118,101>>
  • In the third line we extract the values from binary into the variables. Notice we specify “/bitstring” to catch the remaining value into the variable “F”. Here I would like you to recall the example I gave in the previous section,
1> <<E:1, F/binary>> = <<1:1, 3:7, 2:8, 42:16>>.
** exception error: no match of right hand side value <<131,2,0,42>>

The above failed because the remaining binary didn't have it’s size in multiples of 8. But you can match the above using bitstings,

2> <<E:1, F/bitstring>> = <<1:1, 3:7, 2:8, 42:16>>.
<<131,2,0,42>>

There is a lot more to learn about binaries and bitstrings but it’s beyond the scope for this post. I will surely leave some pointers at the end of this tutorial if you want to learn more about them but I have covered the operational part.

Maps: There is no mystery or learning curve to maps, they are just key value associations. Following is an example of how to create and update a map,

% creating a map
MyFirstMap = #{key1 => "value", <<"key2">> => 4.2, "key3" => <<"bin">> }.
% updating a map
MySecondMap = MyFirstMap#{key1 := "changed", key4 => "value4"}.
  • In the first line we declare a map. Here we have used string, atom and binary as keys and values.
  • In the second line we update the value of keys in the map. Observe that I have shown two ways of updating the keys one with “:=” and other with “=>”. In case you use “:=” when updating a value for a key, then the key must exist but if you use “=>” then it will update the value if they key exists or add the key if doesn't already exist and assign it the value.

So now you have your maps stored in a variable. The next thing you might want to do is extract values out of a map. Here again pattern matching with come in handy. Though you can use the built-in library called “maps”, I will show you how to do pattern matching in maps because I expect you to be able to use library functions,

#{"key3" := Value1, key1 := Value2} = MyFirstMap.

above is how you extract extract values from a map. There are a few things to notice here:

  • To extract values from a map we use the following format “Key := Value”, where “Key” is the key for which we want to extract the value eg. key1 and the value for that key gets stored in Value.
  • Notice when creating a map we use “=>” to assign value to key but when extracting a value we use “:=”.
  • When extracting values from a map the order of keys need not be same to that when you created the map.

Records: A record is a data structure for storing a fixed number of elements. Recall structs from C, records are just like them ! Have a look below

# structs in C
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

Above is how one writes a struct in C and below is how one writes a records in Erlang,

% records in Erlang
-record(books,
{
title,
author,
subject,
book_id
}).

I hope you could see the striking similarities. In general we define a record as follows

-record(Name_of_Record, Tuple_of_Fields).

You can also define a record with default values,

% records in Erlang
-record(books,
{
title = "Book Name",
author,
subject,
book_id = undefined
}).

Here we have defined a record with fields title, author, subject, book_id of which title has a default value of “Book Name” (which is a string) and book_id has default value of undefined (which is an atom). So now you might be thinking where does one define a record and how do you use it ? Have a look below,

-module(testing_records).-export([create_book/4, update_book/2, get/2]).-record(books, { title = "Book Name",
author,
subject,
book_id = undefined }).
create_book(Title, Author, Subject, Id) ->
Book = #books{title = Title,
author = Author,
subject = Subject,
book_id = Id},
Book.
% this function shows how to access values from a record
get(title, Book) ->
Book#books.title;
get(author, Book) ->
Book#books.author;
get(subject, Book) ->
Book#books.subject;
get(id, Book) ->
Book#books.book_id.

% this function shows how to update a record
update_book({title, Title}, Book) ->
Book#books{title = Title};
update_book({subject, Subject}, Book) ->
Book#books{subject = Subject};
update_book({author, Author}, Book) ->
Book#books{author = Author}.

The above module shows how to define, instantiate, access and update a record. Recall function clauses from the previous tutorial to understand how the functions are written. Following is how you use it,

1> c(testing_records).
{ok,testing_records}
2> Book = testing_records:create_book("a brief history of time", "Stephen Hawking", "Universe and stuff", 9).
{books,"a brief history of time","Stephen Hawking",
"Universe and stuff",9}
3> testing_records:get(title, Book).
"a brief history of time"
4> 7> Book2 = testing_records:update_book({title, "Changed"}, Book).
{books,"Changed","Stephen Hawking","Universe and stuff",9}
5>

One thing you might be wondering is that why do the our functions keep returning a tuple when we are using records, for eg.

{books,”a brief history of time”,”Stephen Hawking”, “Universe and stuff”,9}

This is because records are translated to tuple during compilation.

-record(Name, {Field1, ... ,FieldN}).

So, the above records is internally represented as below

{Name, Value1, ... ,Value2}

Where Value1 is the default value for Field1.

The above discussed variable types are heavily used while writing production code in Erlang so try digging in deep because you will surely encounter them in most of source codes you will deal with.

Macros

Macros are pretty well known so I will skip their description and get down to business. Following is how you define and use a macro,

-module(macro_usage).-export([get_macro/0, calulate_area_square/1, name/0]).-define(FIRST_MACRO, "my new macro").-define(AREA(X), X * X).
get_macro() -> ?FIRST_MACRO.
calulate_area_square(Side) -> ?AREA(Side).name() -> ?MODULE.
  • In the third and fourth line we declare a macro. In general we define macro as follows,
-define(MACRO_NAME, DEFINITION).
  • It is rather a convention to use all capital letters in macro names.
  • Both the functions show how to use the two macros defined in third and fourth line. Notice we use “?” before the macro name when we are using a macro.
  • One useful macro that is available to use by default is “?MODULE” which evaluates to the name of module in which it is used. You can see its sample usage on the last line of the above module.

List Comprehensions

List comprehensions are way to modify and filter list elements. Following is the simplest form of list comprehension which squares each element of the list,

[ N*N || N <- [1,2,3,4]].% [1,4,9,16]

List comprehensions are read from right to left. So, we read the above comprehension as; Each element is picked from the list and stored into the variable “N” one by one, the arrow “<-” somewhat guides the element where to go when it comes out of the list ! Once the element is stored in the variable i.e. “N” we apply the operation defined on left side of “||” on this element. We do this for each of the element and the the results are returned in a list.

Following is a more complex example of list comprehension,

1> Weather = [{toronto, rain}, {montreal, storms}, {london, fog},  {paris, sun}, {boston, fog}, {vancouver, snow}].2> FoggyPlaces = [X || {X, fog} <- Weather].
[london, boston]

In the above comprehension we have used pattern matching inside list comprehension ! Here tuples which don’t have their second element as fog won't be able to match and hence will not be stored in variable “X”. So only those tuples that have fog as their second element will get filtered out and exist in the final list. List comprehension can also be applied onto binaries,

1> [ X*X || <<X>> <= <<1,2,3,4,5>>].
[1,4,9,16,25]

Notice we have used “<=” instead of this “<-”, rest is same.

Binary comprehensions

Just like list have list comprehensions, binaries in Erlang have binary comprehension. In the example above the comprehension converted the binary to list but what if we want to keep it as a binary ? Following is how you would write a binary comprehension,

1> << <<(X*X)/integer>> || <<X>> <= <<1,2,3,4,5>> >>.
<<1,4,9,16,25>>

I have almost never seen binary comprehensions being used in production code but I just wanted you to have it in the back of your head that it is possible to use comprehensions over binaries.

Useful stuff

Here I will discuss some common constructs that will come in handy,

Eshell V7.0  (abort with ^G)
1> 1 > 2. % Greater than operator
false
2> 1 >= 2. % Greater than equal to
false
3> 2 =< 2. % Less than equal to
true
4> 2 < 3. % Less than
true
5> "this" == "this". % equal to
true
6> "this" =/= "this". % not equal to
false

Boolean expressions,

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

Following are some built in functions or BIFs which check if a variable is of given type for eg. is_atom/1 check if the argument given to is an atom or not,

is_atom/1 
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_integer/1
is_list/1
is_map/1
is_number/1
is_tuple/1

I think the above are self explanatory but you can still refer here.

Functions revisited

In the previous post I discussed about how to write a function and then we moved on discuss functions with multiple heads or clauses and saw how pattern matching works in functions. Now we move ahead to more evolved discussion of functions.

Guards: these work very much like the real world guards i.e. they restrict the execution of functions to particular cases, for eg.

divide(A, B) when B =/= 0 ->
A/B.

Notice the bold words in the above, that is what we call a guard and the function is only executed when the guard evaluates to true, in general,

function_name(arg1, arg2 ..) when Guards ->
do_something.

Following is a more evolved usage of guards,

right_age(X) when X >= 16, X =< 104 ->
true;
right_age(_) ->
false.
  • In the first function clause we have a clause that allows execution of the clause only if, 16 <= X <= 104. Note that we use comma to separate the two expressions in guards. It only when both of the expressions evaluate to true that the guard returns true. So in some sense “,” is like and operator here. Just like “,” works as and, “;” works as or operator. So in the code below the guard will evaluate true if either X >= 16 or X <= 104,
right_age(X) when X >= 16; X =< 104 -> true;
right_age(_) -> false.
  • In the next clause we use “_” in the argument, it is used to ignore the argument. Just try replacing “_” with “X” and compile the module and you will see that erlang shell will warn you that the variable has not been used. So to suppress that warning we use “_”.

If: Following is how you write “if” statements in Erlang,

if
GuardSeq1 ->
Body1;
...;
GuardSeqN ->
BodyN
end

Notice that “if” can have multiple clauses which are separated by “;” (recall from my previous post that clauses in Erlang end with “;”). The clauses are checked in a sequential manner. If the guard sequence of a clause evaluates to true then “Body” of that clause is executed and the “if” returns.

We now rewrite the previous “right_age/1” using “If”,

right_age(X) ->
if
X >= 16, X =< 104 -> true;
true -> false
end.
  • The first clause checks if X >= 16 and (comma means and) X <= 104 and returns true if it is.
  • The last clause has the guard “true” meaning that if none of the above clause evaluates to true then this clause will surely be executed. You can think of it as the default clause. If you choose to not include the last clause i.e. “true -> false” then in case X does not lie between 16 and 104 then there will be no clause to match and hence “if” will throw an error.

Case: Following is the way one can write case statements in Erlang,

case Expr of
Pattern1 [when GuardSeq1] ->
Body1;
...;
PatternN [when GuardSeqN] ->
BodyN
end

an example for case is given below,

go_to_beach(Temperature) ->
case Temperature of
{celsius, N} when N >= 20, N =< 45 ->
'favorable';
{kelvin, N} when N >= 293, N =< 318 ->
'scientifically favorable';
{fahrenheit, N} when N >= 68, N =< 113 ->
'favorable in the US';
_ ->
'avoid beach'
end.
  • The above function takes “Temperature” as an argument and then uses it is the “case” to match to any of the clauses.
  • Anything between “case” and “of” (“Temperature” in this scenario) will be pattern matched to the clauses sequentially. So “Temperature” will be matched to “{celsius, N}” and if the match fails it will be matched to “{kelvin, N}”, again if this match also fails it will be matched to “{fahrenheit, N}” and if the match it will matched to “_” which will always match because as previously stated “_” will ignore anything and return true. So “_” clause in “case” statements is just like the default “true” clause in the “if” statement i.e. both are bound to return true.
  • Also recall that we use single quotes to write atoms.
  • Lastly notice how the guard statement are written for each of the clauses.

Anonymous Functions:

I think of anonymous functions as throw away functions.

This is because you create them, use them and then they are gone ! To understand what I mean have a look below,

1> Add1 = fun(X) -> X+1 end.
#Fun<erl_eval.6.54118792>
2> Add1(2).
3

The “Add1” variable/function will exist only in the scope of the function you define it in, so as soon as you exit from that function “Add1” won't be available i.e. why I say throw away functions ! In general we write an anonymous function as follow,

fun(Arg1, Arg2 ..., ArgN) ->
do something
end

So, in the previous example we store the anonymous function in the variable “Add1” and later we use it as normal function.

Following is a more evolved example of using anonymous function,

-module(myMap).-export([map/2]).map(Function, List) ->
map(List, Function, []).
map([], Function, Acc) ->
lists:reverse(Acc);
map([ X | Rest], Function, Acc) ->
Ans = Function(X),
map(Rest, Function, [Ans | Acc]).

we use it as follows,

1> c(myMap).
{ok, myMap}
2> myMap:map(fun(X) -> X*X end, [1,2,3,4]).
[1,4,9,16]
  • In our module “myMap” notice that we have defined 2 functions with same name ! we can do this as long as the number of arguments each of these functions take is different. So, Erlang consider map/2 and map/3 as different.
  • Our map/2 function takes a list and function as argument and applies the function onto each element of the list.
  • Also you might notice that I used “[Ans | Acc]” when calling map/3 again i.e. the last line in the module. This syntax basically extends the list by adding “Ans” to the front of it,
1> [ 9 | [4, 1]].
[9,4,1]
  • And since we are adding each evaluated element to the front of the list we lastly reverse the list using “lists:reverse/1” (which is library function) when returning it.

Hufff, this completes almost all of things that I wanted to cover. What I have done in this and the previous post is to introduce very basic building blocks of learning Erlang. I expect you to go ahead and read some Erlang books out there and you should be able to grasp them easily.

Helpful links

If you find it helpful and want me keeping writing more then let me know by liking and sharing it. :-)

--

--