Erlang

Erlang was the first functional programming language I took a shot at learning. I started with the Manning book Erlang in Action which also explained the OTP. The problem was that I was combining learning functional programming with the pretty fringe environment that is Erlang. So instead I took a break from that and decided to learn Haskell, and loved it.

Now I’m back and would like to pick up Erlang and its OTP library. Even if I don’t end up using it, I think it’ll teach me many interesting concepts that lend themselves to this new age of massively concurrent programs. Scala, for example, uses actor-based concurrency much like Erlang’s, as well as supervision trees.

Modules

Modules generally take the following form. The first line contains the module directive, known as the export declaration, as in Haskell. This lists the functions and their arity.

-module(hello).
export([start/0]).

start() ->
  io:format("Hello world~n").

This can be compiled from the shell with the c(modulename) command. However, it’s more likely to be compiled with the Erlang compiler erlc, which produces a beam file. It’s possible to then run the program can then be run on the Erlang virtual machine with the erl command, which uses -s to specify the functions to evaluate, in order.

$ erlc hello.erl
$ erl -noshell -s hello start -s init stop

The following describes a file server. It defines a convenience function start which spawns a process that forever performs the loop function inside a particular directory, which simply waits for a message to be received and processes it. Notice that loop performs a tail-recursive call; Erlang has tail-call optimization, so there’s no need to worry about overflowing the stack.

This server simply responds to two simple messages: one which requests a file listing and another which request file contents. Notice that the messages are distinguished by pattern matching, which works on atoms as well, such as list_dir.

-module(afile_server).
-export([start/1, loop/1]).

start(Dir) -> spawn(afile_server, loop, [Dir]).

loop(Dir) ->
  receive
    {Client, list_dir} ->
      Client ! {self(), file:list_dir(Dir)};
    {Client, {get_file, File}} ->
      Full = filename:join(Dir, File),
      Client ! {self(), file:read_file(Full)}
  end,
  loop(Dir).

The client simply serves to provide client-facing functions that abstract away the server protocol, i.e. the exact messages sent. These simply send the appropriate message and wait for the response.

-module(afile_client).
-export([ls/1, get_file/2]).

ls(Server) ->
  Server ! {self(), list_dir},
  receive
    {Server, FileList} ->
      FileList
  end.

get_file(Server, File) ->
  Server ! {self(), {get_file, File}},
  receive
    {Server, Content} ->
      Content
  end.

Processes

spawn is an Erlang primitive that initiates a concurrent process and returns its identifier.

spawn(ModuleName, FuncName, [Arg1, Arg2, ..., ArgN])

% to spawn a process running the `init` function
% from the `person` module, i.e. `person:init`

spawn(person, init, ["Joe"])

The way in which processes can interact is through sending messages, which is done using the ! primitive.

RecipientPid ! Msg

% self() refers to the current process id

OtherProcess ! {self(), "Hey there"}

Processes are received in a receive block by pattern matching on the messages.

receive
  {From, Message} ->
    ...
end

Values

Values are bound with the = operator, like let in Haskell. It’s also possible to pattern match on the value. Value names start with capital letters. Names beginning with lowercase letters are symbolic constants called atoms 1.

X = 123.

Lists

Lists can be heterogeneous in Erlang.


  1. Like Ruby :symbols and Scala 'symbols.
February 26, 2014
18630dc — October 6, 2018