Week 5: Erlang

This post continues my journey through Seven Languages in Seven Weeks by Bruce Tate. This week's language was Erlang, the first Functional language in the book (although Scala is a hybrid language that incorporates features of the functional paradigm).

Week 1 ˙ Week 2 ˙ Week 3 ˙ Week 4 ˙ Week 5 ˙ Week 6 ˙ Week 7

Erlang seems to be sort of a niche language for telecommunication applications, although it does have some very useful/versatile features (e.g. list comprehension). It takes some obvious inspiration from Prolog as Bruce Tate explains:

Erlang is a functional language. It is strongly, dynamically typed. There is not a lot of syntax, but what is there is not at all like the typical object-oriented languages.
Like Prolog, Erlang has no notion of an object. However, Erlang does have a strong connection to Prolog. The pattern matching constructs and multiple function entry points should look familiar to you, and you handle some problems in the same way, through recursion. The functional language has no notion of mutable state or even side effects.

I also wanted to include the author's explanation of "list comprehension" since that was a feature that really impressed me (apparently other languages have them too, but I've not come across them before):

One of the most important functions in just about any functional language is map. With it, your lists can mutate... Since the feature is so important, Erlang provides a more powerful form that is concise and allows you to do multiple transformations at once.

Here is the summary of this feature from Seven Languages in Seven Weeks:

  • A list comprehension takes the form of [Expression || Clause1, Clause2, ..., ClauseN].
  • List comprehensions can take an arbitrary number of clauses.
  • The clauses can be generators or filters.
  • A filter can be a boolean expression or a function returning a boolean.
  • A generator, of the form Match <- List, matches a pattern on the left to the elements of a list on the right.

Here are some of my notes on Erlang functions, syntax, etc.:

  • Launch the shell with erl and close it with ctrl-c. In the shell, you can compile a .erl file with the command c(filename). then run its functions with modulename:functionname(arguments).; the .erl file has to start with a name for the module (matching the filename) and export statements (as -export([functionname/args]).) for any functions it defines—see the examples below.
  • Every command must end with a period. Comments start with %.
  • Variables must start with a capital letter (like in Prolog). Words that start with a lower-case letter are "atoms", which can be used symbolically but can't have any values assigned to them. Variables are immutable: once a value is assigned, they cannot be changed. The data type does not have to be declared.
  • Variables can contain (anonymous) functions.
  • Values are assigned to variables by pattern matching (similar to the "unification" concept in Prolog). You can pattern match to specific positions in tuples or lists (e.g. with Prolog-style [Head | Tail] syntax). _ is a wildcard if you don't care about the value in a particular position.
  • One really interesting feature was packing and unpacking data to binary. For example <<VarA:4, VarB:4>> would combine VarA and VarB into a single binary number, using four bits for each. The same syntax is used to combine or extract the data (depending on whether the variables have been previously set). I can see this feature being useful for low-level interface tools like ODB2.
  • Define a function with functionname(Parameters) -> expression.. Parameters get pattern-matched like everything else in Erlang, so begin with a capital letter if they are meant to be variables; numbers, strings or atoms (lower-case words) will only get matched exactly. A function can have multiple definitions (e.g. defining its result for a base case then for other cases by recursion, as in the first example below) in which case all but the final definition should end with a semi-colon.
  • The book didn't spend much time on flow control, probably because similar effects can be achieved by giving functions multiple definitions that will pattern-match appropriately. But it did mention case (use as: case Variable of ... -> ...; ... end.) and if (use as: if condition -> result; ... end.). All but the last clause should end with a semi-colon. This page looks like a decent spot for more information.
  • Anonymous functions can be defined with fun(args) -> result end.. Assign them to a variable or use them as a parameter in another function.
  • The book spent some time on list methods. These include foreach, filter, map, foldl, foldr, all, any, takewhile, dropwhile, all of which need to be preceded by lists: (the module name). The list comprehension feature that I mentioned above (and have an example of below) is basically a short form of the map function.
  • The syntax for printing is kind of awkward: io:format("format", [list of items to print]).. In the "format" string ~n represents a newline and ~p prints a given item (other characters preceded by a tilde will also print an item, formatted in specific ways).
  • Processes (functions running independently of each other) are commonly used in Erlang. You can start a process using PID = spawn(fun functionname). and send a message to it with PID ! message. A function that is intended to run as a process can have receive statements which use a similar syntax to case and indicate actions to take when certain messages are received. An exit({reason for exiting}) statement will kill the process when it is called.
  • Bruce Tate gave examples of a process that can monitor or even restart another process, but I only skimmed that part of the chapter because it was more advanced than I felt like getting with Erlang.

As with the previous posts in this series, I have a couple of examples to share. The first one is from Seven Languages in Seven Weeks. The module is called "obligatory" and it defines functions to calculate factorials and Fibonacci numbers (practically an obligatory exercise when learning a programming language). These functions are fact/1 and fib/1, respectively, and they are both defined recursively. The /1 in the function names means they have an "arity" (number of arguments) of 1. As mentioned above, the definition statements with a number as the argument will only match that number, while a capital letter (N) acts as a variable so it can match other cases.

-module(obligatory).
-export([fact/1]).
-export([fib/1]).

fact(0) -> 1;
fact(N) -> N * fact(N-1).

fib(0) -> 1;
fib(1) -> 1;
fib(N) -> fib(N-1) + fib(N-2).

Here is how this module is compiled and the functions are run. Note how Erlang can deal with very large numbers (and these calculations were quick):

1> c(obligatory).
{ok,obligatory}
2> obligatory:fact(6).
720
3> obligatory:fact(200).
788657867364790503552363213932185062295135977687173263294742533244359449963403342920304284011984623904177212138919638830257642790242637105061926624952829931113462857270763317237396988943922445621451664240254033291864131227428294853277524242407573903240321257405579568660226031904170324062351700858796178922222789623703897374720000000000000000000000000000000000000000000000000
4> obligatory:fib(10).
89
5> obligatory:fib(20).
10946
6> obligatory:fib(19).
6765
7> obligatory:fib(18).
4181

As expected, fib(18) + fib(19) = fib(20).

The other example is from the self-study assignments for the second day. The function is only a single line in this case, but it demonstrates list comprehension syntax. The function is getval and it takes a list (of key-value pairs in the form of tuples) and a key (X) as arguments. To the right of the double bars (||) in the list comprehension it matches the contents of the list to the expected structure (key-value pairs in a tuple). This is a generator. After the comma is a filter so that only the given key will match. To the left of the double bars, it simply returns the value that satisfies all of these constraints (but you can also use this feature to return multiple elements with a specified structure):

-module(day_two).
-export([getval/2]).

getval(TupleList, X) -> [{Value} || {Key, Value} <- TupleList, Key == X].

Here is how this module is compiled and the function is called:

5> c(day_two).
{ok,day_two} 
6> day_two:getval([{ruby, "object-oriented"}, {io, "prototypes"}, {prolog, "logical"}, {erlang, "functional"}], erlang).
[{"functional"}]

I didn't really look up links for further reading on Erlang, but if this language interests you, here's the guide for getting started.

I've sort of rushed through the last two languages, partly because I've been really busy with other stuff and partly because they didn't catch my interest as much (i.e. Erlang seems to mainly get used for communication and database applications which are not really what I'm into)—although I did find Erlang was a decent way to get my toes wet in the functional paradigm. However, the next two languages (Clojure and Haskell) are ones that seem rather intriguing, so I'm looking forward to going through them. They continue with this paradigm and wrap up the book. Before I get to them, though, there will probably be an interlude with a post or two not in this series.

Permalink