Erlang is an intimidating language. It’s functional – which is enough to make it intimidating – terse, uses unfamiliar syntax, and just “feels” altogether alien. Even the Erlang website is intimidating. When you click the “Erlang Quickstart” button on the main page you might expect an introduction to the language through a simple “Hello, World!” tutorial. Instead, what you are given is a lengthy “guide”, the “Introduction” to which has no intention of introducing you to anything other than explaining what the guide isn’t going to cover. The remainder of the guide leaves one with the sense of being thrown into the deep end of the pool, and is written a manner as terse as the language itself.
But as the say, “Don’t judge the book by the cover.”
I won’t make the argument that Erlang isn’t intimidating. Instead, this article will show you enough of the language to enable you to use Erlang libraries in your Elixir projects, read and understand the documentation, and at least read Erlang at a basic level. By the end, you’ll be able to determine for yourself if Erlang is truly as intimidating as it’s been made out to be.
This isn’t a tutorial, but you may still find it valuable to experiment with the topics and concepts covered here. To that end, you may want to use Erlang’s REPL to test out individual commands or load and compile Erlang files. If you’re familiar with IEx, you’re 80% of the way there. The only difference is the language and some of the commands.
Like IEx and every other REPL, the Erlang REPL is useful for testing out short
commands, working with local files and modules, and using it as a sort of
scratch pad. And just like IEx, you can exit with ctrl-c, ctrl-c
or ctrl-c,
a
.
Assuming you have Elixir and/or Erlang installed, just type erl
at the Unix
prompt.
% Try it out:
1> io:format("Hello world~n").
2> 6 * 9 = 42.
3> reindeer_flotilla = 812.
4> ReindeerFlotilla = 812.
5> Double = fun(X) -> 2 * X end.
6> Double(8).
You can also “load and compile” files in the REPL with the c/1
, just like in
IEx. Here’s a simple “Hello, World!” module.
% hello.erl
-module(hello).
-export([start/0]).
start() ->
io:format("Hello, world!~n").
Now, in the REPL, load it with the c/1
command:
1> c("hello"). % can also use c("hello.erl").
2> hello:start().
Already you’re seeing some peculiarities with Erlang, and now that our playground is set up we can explore those peculiarities in more detail.
Learning a new programming language, much like learning a new spoken language, not only requires learning new words, but oftentimes a whole new way of organizing your thoughts. That’s what I experienced when transitioning from perl to Ruby and then from Ruy to Elixir (transitioning from C to perl was super easy, however, and may say more about me than I want to admit). Programming languages are also like spoken languages in that they echo the languages which came before them. Elements of German, Latin, and Greek are all present in English. Likewise, elements of Erlang are present in Elixir in their original form, while other elements have either been modified and expanded, or are unique to Elixir.
As we look at how Erlang implements aspects of the language pay close attention to how it is both similar and different from Elixir. Seeing and understanding both will help you understand the choices that were made when creating Elixir and also understand why things are the way they are in Elixir.
Perhaps the first thing you will notice when looking at Erlang code is the difference in variable naming. As has been shown in the above examples, Erlang variable begin with a capital letter. This is very different from other languages which reserve such naming for constants, modules, classes and the like. To be clear, variables must begin with a capital letter.
% Try it out
1> foo = 12.
** exception error: no match of right hand side value 12
2> Foo = 12.
12
Another difference between variables in Erlang and other languages is that variables can only be assigned once.
In Erlang, variables are just like they are in math. When you associate a value with a variable, you’re making an assertion–a statement of fact. This variable has that value. And that’s that.
– Joe Armstrong, Programming Erlang
Because variables can only be assigned once, the standard practice is to append a number to the end of a variable to “reassign” a variable.
% Try it out
1> TheAnswer = 42.
42
2> TheAnswer = 6 * 9.
** exception error: no match of right hand side value 54
3> TheAnswer1 = 6 * 9.
54
Unlike Elixir, atoms in Erlang aren’t prepended with a :
. Instead, Erlang
atoms are named much like you would name a variable in Elixir. So an atom must
begin with a lower case letter, can include numbers, underscores, and the @
character. Also, unlike Elixir, to define an atom with spaces or begin with a
capital letter, you use single quotes.
% Try it out
1> is_atom(particle_man).
true
2> is_atom('Particle Man').
true
3> triangle_man = "Universe Man".
** exception error: no match of right hand side value "Universe Man"
4> is_atom(user@foo).
true
5> :person_man.
* 1: syntax error before: ':'
Like in Elixir, module names are atoms, which is why we can use Erlang modules
and functions in Elixir by prepending their names with a :
.
iex 1 > :io.format("Hello, World!~n")
Hello, World!
:ok
iex 2 > :calendar.universal_time
{{2019, 7, 11}, {18, 25, 50}}
After noticing the differences in capitalization with variable and atom naming,
the next thing which stands out is how terms and statements are punctuated.
Following written language, statements – as you have no doubt noticed – end
with a .
(full stop). Functions are called through a module by use of a :
(ex: io:format("Hello, World~n")
). Semicolons, rather than ending statements,
are used to separate “function definitions and in case
, if
, try..catch
,
and receive
expressions.” Lastly, the ,
as expected, is used to separate
arguments and patterns.
There’s an easy way to remember this–Think of English. Full stops separate sentences, semicolons separate clauses, and commas separate subordinate clauses.
– Joe Armstrong, Programming Erlang
As in Elixir, Strings in Erlang can be represented “as a list of integers or as
a binary”. When represented as a binary, the traditional use of double-quotes
("
) is used. However, whereas Elixir uses single-quotes to represent character
lists, Erlang only offers lists of integers. As we’ve seen, single-quoted
strings are atoms in Erlang.
% Try it out
1> "Foo Man was here.".
"Foo Man was here."
2> [83, 104, 97, 122, 97, 109, 33].
"Shazam!"
3> is_binary([83, 104, 97, 122, 97, 109, 33]).
false
4> is_list([83, 104, 97, 122, 97, 109, 33]).
true
“Strictly speaking, there are no strings in Erlang.” This makes finding relevant documentation for your tasks…challenging.
Another difference between Erlang and Elixir – and just about every other
language – are the comments. Erlang uses the %
character the same way Elixir
uses #
. Everything after the %
is commented out.
In some ways this makes sense. The percent sign isn’t used for anything other
than showing a percentage which is done so in a string. Also, Erlang uses the
#
character for use with records and maps. While other languages use %
as the modulo operation, Erlang uses the named operator rem
.
% Try it out
1> 'not a comment' % is a comment
1> .
'not a comment'
2> 5 rem 2.
1
3> 5 % 2
3> .
5
Since we’ve stumbled into the arena of math operators in our discussion about comments, it makes sense to look more closely at them and other operators as well. Erlang does things differently in about every case. Rather than discuss each operator – which you already understand – I’ll list the operator used in Elixir, Erlang, and the description.
Elixir | Erlang | Description |
---|---|---|
+ | + | Addition |
- | - | Subtraction |
* | * | Multiplication |
/ | / | Floating point division |
div | div | Integer division |
rem | rem | Integer remainder |
Elixir | Erlang | Description |
---|---|---|
not |
not |
NOT |
and |
and |
AND |
or |
or |
OR |
N/A | xor | XOR |
Elixir | Erlang | Description |
---|---|---|
bnot or ~~~ |
bnot | NOT |
band or &&& |
band |
AND |
bor or ||| |
bor |
OR |
bxor or ^^^ |
bxor |
XOR |
bsl or <<< |
bsl |
Left shift |
bsr or >>> |
bsr |
Left shift |
Note: To use bitwise operators in Elixir, you must require
or use
the
Bitwise
module.
Elixir | Erlang | 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 |
Note: Pay close attention to the “less than or equal to” operator
The difference between anonymous functions in Erlang (called “funs”) and Elixir
is all of two characters: “u” and “;”. Where Elixir uses the fn
keyword,
Erlang uses fun
. In Erlang, function bodies are separated with a ;
, but in
Elixir, there’s no need.
# Try it out in Elixir
x = fn
{:ok, _file} -> IO.puts "opened the file"
{_, _error} -> IO.puts "unable to open the file"
end
#Function<6.99386804/1 in :erl_eval.expr/5>
% Try it out in Erlang
X = fun
({ok, _file}) -> io:format("opened the file~n");
({_, _error}) -> io:format("unable to open the file~n")
end
#Fun<erl_eval.6.99386804>
As you can see, there is virtually no difference between Erlang and Elixir when
defining anonymous functions. I should mention one more thing. In Elixir, when
you assign an anonymous function to a variable, you must separate the variable
from the parentheses with a .
. This isn’t necessary in Erlang.
% Try it out
1> X = fun(Y) -> 2 * Y end.
#Fun<erl_eval.6.99386804>
2> X(4).
8
As you work with Erlang libraries in your Elixir applications, you’ll rarely run across documentation requiring you to understand module syntax. However, in the event that you do, you’ll want to be mindful of the differences.
The first difference you’ll note is the module declaration. In Elixir this is
performed with the defmodule
block. In Erlang, however, a module is declared
with the “module attribute” (-module(module_name).
) and must be named the
“same as the filename minus the extension .erl
.”
Another difference between Erlang and Elixir modules is the treatment and syntax of functions. By default, all functions within an Erlang module are private and must be “exported” in order to use them outside the module. Functions also differ in their syntax as they more closely follow the syntax of “funs” (i.e. anonymous functions). The difference here being that whereas a single “fun” can have multiple bodies, a module function must be re-declared with each new body.
# Try it out
# fact.ex
defmodule Fact do
def fact(0), do: 1
def fact(n), do: n * fact(n-1)
end
% Try it out
% fact.erl
-module(fact).
-export([fact/1]).
fact(0) -> 1;
fact(N) -> N * fact(N-1).
List comprehensions are an underutilized feature of Elixir. In a single line,
you can combine multiple collections, filter
, map
, and return a unique set
into a new collection. What you can’t do is make all of that terribly readable.
Given their obfuscated nature and just how comprehensive the Enum
module is,
perhaps it should come as no surprise they’re underutilized. Still, consider
this example from the Plug library:
# List comprehension
defmodule Plug.Conn do
...
def get_resp_header(%Conn{resp_headers: headers}, key) when is_binary(key) do
for {^key, value} <- headers, do: value
end
...
end
# Example usage:
Plug.Conn.get_resp_header(conn, "Set-Cookie") # returns list of cookies
Here, in a single line we are able to both match and filter given a key, and
it’s accomplished in a single line. Now, consider what this would look like with
the Enum
module:
# Enum
defmodule Plug.Conn do
...
def get_resp_header(%Conn{resp_headers: headers}, key) when is_binary(key) do
headers
|> Enum.filter(fn {k,v} -> k == key end)
|> Enum.map(fn {_, value} -> value end)
end
...
end
To make comprehensions more confusing, you can use multiple “generators” (the
value <- list
portion) to create a Cartesian product.
# Try it out
1> letters = ~w[a b c d e f g h]
2> numbers = [1, 2, 3, 4, 5, 6, 7, 8]
3> chessboard = for l <- letters, n <- numbers, do: "#{l}#{n}"
Of course, things are a little different with Erlang. In Elixir, comprehensions
are shaped like functions: function name (i.e. for
), arguments (i.e.
generators), result block. In Erlang, however, they’re shaped like lists with
the result block first and then the generators. Personally, I prefer Erlang’s
shape more.
% Try it out
1> Headers = [
1> {"Set-Cookie", "Cookie1"},
1> {"Set-Cookie", "Cookie2"},
1> {"Content-Length", 2112}
1> ].
2> [Value || {"Set-Cookie", Value} <- Headers].
3> Letters = ["a", "b", "c", "d", "e", "f", "g", "h"].
4> Numbers = [1, 2, 3, 4, 5, 6, 7, 8].
5> Chessboard = [ lists:concat([L, N]) || L <- Letters, N <- Numbers].
spawn
, receive
, monitor
, even Erlang’s gen_server
module: for the most
part, there is little difference between Erlang’s process functions and those in
Elixir. The one exception to this is Erlang’s send operator: !
.
The !
operator is no different than Erlang’s erlang:send/2
function. Both
send a message to the destination pid
. Nevertheless, erlang:send/2
isn’t
referenced in Programming Erlang, Learn You Some Erlang, or in numerous
articles I’ve run across. I have to conclude that !
is the preferred method
for message passing.
% Try it out
% summer.erl
-module(summer).
-export([loop/0]).
loop() ->
receive
[X, Y] ->
io:format("~B + ~B = ~B~n", [X, Y, X+Y]),
loop()
end.
% In the Erlang REPL
1> c(summer).
{ok,summer}
2> Pid = spawn(summer, loop, []).
<0.66.0>
3> Pid ! [8, 11].
8 + 11 = 19
[\b\v]
4> erlang:send(Pid, [8, 11]).
8 + 11 = 19
[\b\v]
Not every difference between Elixir and Erlang relates to grammar and syntax, some differences are in naming and conventions. An example of this is the acronym “MFA”. MFA stands for “Module, Function, Argument”. Although this acronym occasionally appears in the Elixir Forum and other discussion areas, it’s not as common as within the Erlang community.
Another acronym you are likely to see is “BIF”. BIFs are “built-in functions”
and “are defined as part of the Erlang language”, (i.e. functions within the
erlang
module). “[T]he most common BIFs…are autoimported”, while those which
are not – such as erlang:send/2
mentioned in the Process section – must be
called with the erlang
module.
If you are coming to Erlang from Elixir it may come as a surprise to you that Erlang has no concept of Pipelines. Because of this, many Erlang developers, such as Brujo Benavides as discussed in ElixirMix Ep. 60, find themselves adding pipelines to their Elixir code only after writing the initial implementation.
Perhaps this difficulty with pipelines stems from another “oddity” of Erlang. As you read the documentation, you will find the convention is to pass functional predicates as the first argument rather than the last as in Elixir. For those of us coming from Elixir or Ruby, passing a function as the first argument seems completely foreign.
Documentation is a difficult problem. It’s not just a problem of explaining how something works – which is difficult in its own right – but answering “who is the audience?”, “how should it be organized?”, and “what and how much information should be shared?” Every language, framework, and technology answers these questions even if it’s not a conscious decision on the documenter’s part.
Like the language itself, Erlang’s documentation is terse and expects a certain
level of competency on the part of the developer. For example, this is the
Erlang documentation for lists:all/2
:
Returns
true
ifPred(Elem)
returnstrue
for all elementsElem
inList
, otherwisefalse
.
Contrast this with Elixir’s documentation:
Returns true if fun.(element) is truthy for all elements in enumerable.
Iterates over the enumerable and invokes fun on each element. When an invocation of fun returns a falsy value (false or nil) iteration stops immediately and false is returned. In all other cases true is returned.
Examples
iex> Enum.all?([2, 4, 6], fn x -> rem(x, 2) == 0 end)
true
iex> Enum.all?([2, 3, 4], fn x -> rem(x, 2) == 0 end)
false
iex> Enum.all?([], fn x -> x > 0 end)
true
If no function is given, the truthiness of each element is checked during iteration. When an element has a falsy value (false or nil) iteration stops immediately and false is returned. In all other cases true is returned.
iex> Enum.all?([1, 2, 3])
true
iex> Enum.all?([1, nil, 3])
false
iex> Enum.all?([])
true
To be clear, I am not saying Erlang’s documentation is bad or even incomplete. It’s not. It is, however, different from what many in the Elixir community are used to.
Finding the correct Erlang documentation isn’t straightforward. From what I can tell, there are two reasons for this. The first reason is that unlike many languages, Erlang grew out of Prolog rather than C and inherited its library organization from there. The second reason is based on how Erlang handles modules. As we’ve already seen, Erlang modules must be named the same as the file in which they reside. Because of this, Erlang doesn’t have the idea of namespacing, resulting in all of erlang’s modules organized in a single space and without a level of importance.
If you’re looking for a specific function to perform a task, look first for a
module that covers the topic – If you’re dealing with a list, look in the
lists
module. From there, scan the available functions and see what most
closely matches what you’re looking for. Keep an open mind, just because the
function doesn’t look like it would make sense, it just might be what you need:
example: integer_to_list/1
converts integers to strings (remember, strings are
handled as lists in Erlang). Also, remember that many functions developers are
most likely to need are stored as BIFs in the erlang
module.
If you still can’t find what you’re looking for, the Internet is your best choice. Better yet, the chat room of your local Elixir/Erlang group.
Good books are over your head; they would not be good for you if they were not.
― Mortimer J. Adler, How to Read a Book
Once you’ve found the documentation you’re looking for, the next obstacle is getting something useful out of it. Generally speaking, all Erlang documentation follows the same basic pattern:
module_name Module
Module Summary
Description
Exports
There is some amount of ceremony in Erlang documentation. For example, listing the module name twice and providing a summary along with the full description is redundant. Aside from that, the only thing to be mindful of at this top level is the use of “Exports” instead of “Functions”. This goes back to the way Erlang handles functions inside modules: they’re private unless exported, hence the section name, “Exports”.
When we zoom in on the documentation for a particular function, however, things
get more complicated. Let’s look at lists.all/2
:
all(Pred, List) -> boolean()
Types
Pred = fun((Elem :: T) -> boolean())
List = [T]
T = term()
Returns true if Pred(Elem) returns true for all elements Elem in List, otherwise false.
In the first line we see two things: 1) the function definition all(Pred, List)
; 2) the return value, boolean()
, shown by the arrow (->
).
The next section lists the “Types” involved in the function, and goes into some
detail. In the example we see Pred
used for a “functional predicate”. In this
case the function must return a boolean (true/false) value. Also note that
although T
is not used in the function definition, it is used when defining
both Pred
and List
, and therefore receives its own definition under “Types”.
Lastly we have the description of what the function does and how to use it. What’s interesting to note here is what is left out: example usage. As I’ve skimmed the Erlang documentation I’ve noted that less than 50% of the modules and functions provide example usage.
This may come as a surprise, but you can easily include Erlang files in your Elixir projects. It makes sense. Afterall, if you can include Erlang libraries as dependencies, why wouldn’t you be able to include them as part of your project.
To do this, create a src
directory at the root level of your Elixir project.
From here you can drop in Erlang files and they’ll be available to your project.
You can even organize the files by nesting directories within the src
directory.
% Try it out
% hello.erl
-module(hello).
-export([start/0]).
start() ->
io:format("Hello, world!~n").
Add the module above to your project by following these steps:
$ mkdir src
$ cp path/to/hello.erl src
$ iex -S mix
# IEx starts
1> :hello.start()
Hello, world!
:ok
Of course you don’t have to store the Erlang files under src
, you can change
it. To do so, just add that configuration to the project/0
function of your
mix.exs
file:
def project do
...
erlc_paths: ["lib"],
...
end
With this in place, you can now freely mix Erlang and Elixir files under the same directory structure in your project. By the way, don’t do this.
Erlang is thought by many to be an intimidating language, but is it? Certainly its syntax and grammar are different, having been heavily influenced by Prolog, but as we’ve seen it’s only slightly different from Elixir’s syntax which many regard to be simple and expressive. Are the two so different?
True, the language is more terse than Elixir, but only just so. In many ways the two languages are nearly identical. If there is any glaring difference, it’s to be found in the documentation. Whereas Elixir bends over backwards to give developers the information they need to be productive, Erlang provides them with just enough and expects them to make up the difference.
There’s an old UNIX joke that says, “UNIX is user-friendly; it’s just picky about its friends.” I think the same can be said of Erlang. Is Erlang an intimidating language? It is to those who merely glance at it. However, to those willing to put in a little more effort, it readily offers up its wealth and power.
Many thanks to Amos King and Sean Cribbs for proof reading and providing necessary corrections.