In this tutorial, I'll cover the tools that I use the most in my day-to-day life as an Erlang developer. All or most of the topics that make the cut share the following characteristics:
Like many other languages, the most common way to interact with an Erlang VM is through the Erlang shell. You can use it to test code, learn how to use Erlang, and debug production systems. The Erlang shell is a powerful thing, but you can add much more power to it with the three tools that I will show you below.
In general, to evaluate functions outside a module in Erlang you have to prepend their names with the corresponding module name. For example, if you want to sum up a list, you have to do the following:
11> lists:sum([1, 2, 3]).
26
32>
But you'll notice that for certain functions in the shell, you don't need explicit definitions. For example:
11> c(your_module).
2{ok,your_module}
32> h().
41: c(your_module)
5-> {ok,your_module}
6ok
73> e(1).
8{ok,your_module}
94>
Where do such functions live? They're defined in the shell_default module. You can add your own default functions as well. To do that, you have to create and load a module called user_default
. As an example, I like to run bash commands when using the Erlang shell; I don't want to leave the shell just to copy a file. Erlang/OTP gives you a a function to do that: os:cmd/1
, but that will not print out the output of your command. Instead this command will return that output as a string. You have to print the string to see the output properly.
So, using user_default
, I added the following lines to my user_default.erl
file:
1-module(user_default).
2-export([cmd/1].
3
4cmd(Cmd) -> io:format("~s~n", [os:cmd(Cmd)]).
Now I can run, execute, and print the results of os:cmd/1
using my shell:
11> c(user_default).
2{ok, user_default}
32> cmd("cat user_default.erl").
4-module(user_default).
5-export([cmd/1].
6
7cmd(Cmd) -> io:format("~s~n", [os:cmd(Cmd)]).
8
9ok
103>
But then, I have to manually load user_default
in every single shell that I open. ~/.erlang
to the rescue! ~/.erlang
is a file that works almost like ~/.bashrc
for bash. Upon starting, the shell reads ~/.erlang
and evaluates each one of the expressions it finds there as if they were inputted to the shell.
So, I added the following lines to that file:
1UD = "/path/to/my/user_default".
2shell_default:c(UD).
My ~/.erlang
is actually more complicated than that, but the shell_default
should give you the gist of my modifications. Now, every shell that I open comes with support for cmd/1
.
One of the most annoying things about the Erlang shell is that it does not keep track of command history very well. In fact, default settings will erase all command history upon closing the shell. I've seen many devs working around this limitation by having a notebook of erlang expressions from which they copy & paste into the console every time. (Some of them use user_default
and/or ~/.erlang
for that.)
But still, it's not a real solution, right? To properly fix the issue, right after installing a new version of Erlang/OTP, I install erlang-history. It's a really tiny almost-invisible hack to the Erlang/OTP distribution with one simple purpose: to keep track of your previous commands in your Erlang shells and let you reuse them. Simple, yet amazingly useful.
As soon as you move past the simple examples and into the realm of OTP apps, you'll be much better off working with a build tool that can help you organize your code, manage dependencies, build releases, run tests, and more.
When working with Erlang, you can either embrace the Makefile or try get as far away from it as possible.
If using Makefiles to build and manage your projects seems natural to you, you will probably like erlang.mk. erlang.mk
is basically a big Makefile script that you can include in your own Makefile with something like:
1PROJECT=your_app
2include erlang.mk
With that, you get access to many commands, like
$ make
to build your project$ make tests
to run your test suites$ make rel
to generate a releaseYou can find the whole list using $ make help
, and you can, of course, add more by just extending your Makefile.
The best thing about erlang.mk
is that it provides the necessary support for building almost any existing Erlang library, regardless of the tool that library owners use to maintain it. With erlang.mk
you can include applications built with rebar, rebar3, erlang.mk (as expected :P), ad-hoc Makefiles, and others in your depositories. You can download deps from github, hex.pm, bitbucket, your local filesystem and many other places. Clearly erlang.mk
makes Erlang vastly more versatile.
Now, if Makefiles are not your thing and you prefer to use the official build tool (since March 2016) sponsored by the Erlang/OTP team, then rebar3 is your way to go.
rebar3 is written entirely in Erlang and the idea is that you can use it without adding any Makefile to your project.
Once you install it in your system, you should add a rebar.config
file in the root folder of your project. A minimal one looks something like this (although even the options below are optional):
1{deps, []}.
2{erl_opts, [debug_info]}.
3{cover_enabled, true}.
You can find the list of all the options you can speficy here. Then, you can run commands like the ones below in your shell:
$ rebar3 compile
to fetch deps and build your project$ rebar3 ct
to run your tests$ rebar3 release
to generate a releaseOf course, $ rebar3 help
gives you the list of all available commands. And, just like erlang.mk
, rebar3
is also extensible through plugins.
One of the best things about rebar3
is that, by using rebar.lock
, it provides repeatable builds. That way, once you're sure your project works as expected, you can be sure it will keep working as expected no matter how many times or in how many different places you compile it.
I've personally stated a couple of times online (in interviews and blog posts) how important it is (especially if you work on open-source projects) to maintain high code quality. Erlang comes with several tools that will help you with that. Erlang tools can even help you detect bugs that you'll probably find at runtime… without running your system!
Dialyzer is a static analysis tool that will help you identify type inconsistencies in your code, like when you are passing a binary()
as the parameter to a function that expects a string()
. This used to be a very very slow tool, but it has since been highly optimized and now runs smoothly, especially if you run it frequently. Plus, it's integrated in both rebar3
and erlang.mk
. (Follow the links to learn more.)
If you use dialyzer (dia.erl
), you'll see results like the one below.
1 Checking whether the PLT dia.plt is up-to-date... yes
2 Proceeding with analysis...
3 compile (+0.09s): 0.02s ( 1 modules)
4 clean (+0.00s): 0.00s
5 remote (+0.00s): 0.18s
6 order (+0.00s): 0.00s
7 typesig (+0.12s): 0.01s ( 3 SCCs)
8 order (+0.00s): 0.00s
9 refine (+0.00s): 0.01s ( 1 modules)
10 warning (+0.00s): 0.00s ( 1 modules)
11 (+ 0.71s)
12
13dia.erl:5: Function bad/0 has no local return
14dia.erl:5: The call lists:flatten(<<_:168>>) will never return since it differs in the
15 1st argument from the success typing arguments: ([any()])
16 done in 0m1.16s
17done (warnings were emitted)
In this case in line 5 of dia.erl
, we are calling lists:flatten/1
with a 168-bit long binary (<<_:168>>
) when that function expects a list ([any()]
) as its parameter. This warning was not raised at compile time, but using dialyzer you can find it before it pops up during run time.
A much simpler tool that's been packed in the Erlang/OTP distribution is xref
. xref
is a cross-reference tool that analyses your code and finds calls to unexistent functions, deprecated functions and other similar things. It doesn't search as deeply as dialyzer, but it will spot obvious bugs in no time.
xref
is also integrated with rebar3
and erlang.mk
(The documentation is not there yet, but it uses xref_runner
to check your code).
xref
can find errors like:
1…
2===> Running cross reference analysis...
3===> Warning: your_module:your_function/1 calls undefined function this_function:doesnt_exist/1 (Xref)
4…
There xref
detected that we're calling a function that doesn't exist from within the code of your_module:your_function/1
. Again, that's something that the compiler will not warn you about.
Besides analyzing your code in search for bugs with xref
and dialyzer, you can ensure that the code in your project is maintainable. elvis is a command-line tool (and an Erlang application as well) that you can use to verify the compliance of your code to certain style rules, which you pre-define in your elvis.config
file. There is a plugin for rebar3
and another one for erlang.mk
. You can also use elvis online to check your github pull requests.
Its output looks like the following:
1Loading files...
2Loading src/dia.erl
3Applying rules...
4# src/dia.erl [FAIL]
5 - no_tabs
6 - Line 33 has a tab at column 0.
In this case, elvis is detecting the usage of tabs for indentation which is something that's specified as invalid in the project's elvis.config
file.
The next group of tools assume that you already created your app, packed it up as a release, and deployed it to some server. They also assume that the app's been running for a while, but has started behaving strangely. So you need to debug it.
First, you connect to your server with a remote shell (See the link for the -remsh
command). Then, you use Erlang's dbg.
Alas, dbg is not a very intuitive, simple debugging tool. It's really powerful, but there are simpler tools for the task at hand. I'll show you 2 of them below.
redbug is a debugging application which is quite similar to dbg, but with a more intuitive interface. It comes with eper but, to be honest, redbug
is the only eper
component I've ever used. With redbug
, you can specify a trace pattern as a string, and it will print out a message in your console everytime a function call matching that pattern is evaluated. It looks like this:
11> redbug:start("your_module:your_private_function->return").
2{156,1}
32> your_module:your_public_function().
4% 19:55:12 <0.1.0>({erlang,apply,4})
5% your_module:your_private_function(some, parameters)
6
7% 19:55:12 <0.1.0>({erlang,apply,4})
8% your_module:your_private_function/2 -> ok
9ok
103>
I actually tend to always use the same options when starting redbug
. You can check the available startup options using redbug:help()
. As such, I've added this start code to my user_default (Don't do this in production, kids!!):
1redbug(What) ->
2 catch redbug:stop(),
3 timer:sleep(100),
4 redbug:start(What, [{time, 9999999}, {msgs, 9999999}, {print_msec, true}]).
Now I can easily get redbug
going.:
14> redbug("your_module:your_private_function->return").
2{156,1}
35> your_module:your_public_function().
4% 19:59:13 <0.1.0>({erlang,apply,4})
5% your_module:your_private_function(some, parameters)
6
7% 19:59:13 <0.1.0>({erlang,apply,4})
8% your_module:your_private_function/2 -> ok
9ok
106>
But tracing function calls is just one of the many things you can do to check the health of your Erlang servers. Fred Hebert recollected tons of experiences dealing with that in both a great book (Stuff Goes Bad: Erlang In Anger) and a great tool: recon.
With recon you can do many things, you can: trace function calls (just like redbug
), recover the source code of your compiled modules, check app memory consumption, figure out message queue lengths, clean up binary memory, find leaks and much more. I strongly recommend you to read the book and play around with the different recon
tools described in it. You'll want to know them by heart if your server starts behaving erratically.
Now some pointers that don't really fit in the list above, but deserve to be mentioned anyway.
If you use TDD (even if you just use common test to test your Erlang apps and you are interested in maintaining your code quality using the tools I mentioned above, you have to check katana-test. With the code below, you'll be dialyzing, xref
ing, and rocking your code every time you run your tests.
1-module(your_meta_SUITE).
2
3-include_lib("mixer/include/mixer.hrl").
4-mixin([ktn_meta_SUITE]).
5
6-export([init_per_suite/1]).
7
8init_per_suite(Config) -> [{application, your_app} | Config].
No list of great Erlang things will be complete without a shout out to the great community of developers behind it. If you're going to work on Erlang or even if you're just approaching the language and you want to know what it's all about, you have to meet the erlangers.
There are a couple of ways to accomplish that:
If you're serious about Erlang development, keep an eye on those platforms and stay connected with us for updates and other opportunities!
Finally, I want to mention the Erlang Guidelines, published by Inaka on github. As I said in the mailing list when we first talked about them:
erlang_guidelines is the set of standards that we (at Inaka) enforce in our Erlang code. … It will only reflect the ideas of the ~8 erlangers that were/are working at Inaka. We accept contributions because we love good and constructive debate, but we don’t expect anybody … to like our guidelines :)
In other words, the guidelines on that repo don't need to be your guidelines, but you should have some guidelines of your own. Forking that repo and then adapting its contents to your ideas may be a good way to start. Others have done exactly that already. As Iñaki once explained:
Code formatting, spacing, layout and "making the code look good" is not the goal of having guidelines. Similarly, the idea is not to engage in bondage programming in which everyone is forced to do something they don't want, but to have a baseline of order and agreed-upon-terms.
The purpose of having consensus and following it is to free up energy, not to consume it in favor of poorly-defined or subjective goals such as "it looks better". Having each one one make up his/her own rules "and damn the rest" is just as antisocial as forcing everyone to adhere to strict guidelines which dictate where you put your commas or spacing. Discipline provides freedom to the group, because once you achieve consensus on a matter, the effort of dealing with other's trespasses is greater than the effort of keeping oneself from trespassing.
Issues such as line length restrictions are a means to an end: they make the code easier to change, debug, and extend to respond faster to requirement changes. To agree with any of this, one must acknowledge that programming is a social endeavor, and group behaviour applies. It may be easier to write for you, at this time, but others will have to read it. This is the sole objective of code guidelines.
Hopefully this guide cleared up some of the questions you may have had regarding Erlang-based application development and also boosted your interest in creating Erlang apps! Thank you for reading.