Amtal

Erlang Shell Finesse

Erlang isn’t a programming language with libraries for distribution, but a distributed operating system with a programming language.

It’s an OS. Which makes the erl shell far more than a REPL, just as bash is more than an interpreter for bash-script. It is your (sometimes only) window into a complex operating system, so skillful usage is important.

1 Builtin Functions

The help() function lists your immediately available commands.

These are full of goodies. Most commonly used will be l(Module) for updating code on the fly, but gems like the record functions can be extremely useful. Compare the two ‘dict’ printouts:

6> dict:new(). 
{dict,0,16,16,8,80,48,
      {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
      }
7> rr(dict), dict:new().
#dict{size = 0,n = 16,maxn = 16,bso = 8,exp_size = 80,
      con_size = 48,
      empty = {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
      segs = }

This is the reason why centralizing all of a project’s cross-module records in one .hrl file can sometimes be a good idea!

Another awesome helper is v(N), fetching the value returned by the Nth query. (The prompt displays the query number.)

8> v(7).
#dict{size = 0,n = 16,maxn = 16,bso = 8,exp_size = 80,
      con_size = 48,
      empty = {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
      segs = }
9> dict:size(v(7)).
0

Play with the various functions and get used to them – the shell is a very efficient debugging and exploration tool when combined with hot code reloading and a trace library like redbug.

And oh yeah, install redbug! It’s faster than a debugger, and smarter than putting unnecessary print statements (or worse, ?LOG/?DEBUG macros) in your code. Erlang is a minimalist language – redbug:help() should get you started on the path of tracing only what you need, instead of logging everything you might want.

2 Efficient Editing

Hitting tab auto-completes module:function() calls, partially if there’s multiple matches, printing all matches if partial completion’s impossible. This synergizes well with Erlang’s need for long module names. Try typing a[TAB]:w[TAB]). in the shell!

The terminal doesn’t understand Home/End keys, or Ctrl+Arrow word movements. You can remedy this by installing rlwrap, short for “ReadLine wrapper”, and aliasing erl to rlwrap -a erl. It wraps anything – you can rlwrap -a ssh and get nice line editing on the most neolithic servers.

Entering multi-line terms prints the prompt mid-entry, which sucks if you want to copy them elsewhere. The h() history, however, doesn’t suffer from this problem.

3 Surprises Suck

Remember, immutable variables. If you assign to a name, you can’t re-assign without forgetting it first with f(VarName). If this seems obvious, wait ’til you accidentally try to pattern match on an already bound variable. Try and explain that error message…

Due to the peculiar constraints imposed by distribution and hot code reloading, Erlang functions belong to the modules they’re defined in and don’t budge. You can’t define them in a shell, you must use funs. Only funs, assigned to shell variables. You can still build up a nice collection of copy-and-paste helper functions, they’ll just all start with capital letters.

If things seem strange and broken when playing with ETS tables or other things that link to the caller process, it’s because by default the shell restarts the evaluator process on exception. Calling catch_exception(true) will change this behavior, letting links live.

4 Distribution

Distributed operating system. A single Erlang VM process is a ‘node’. Nodes can be connected together into a cluster, and processes won’t even realize they’re talking between different machines.

You can get the redundancy and scaling benefits of distribution with zero code overhead! A shared password and some unique names are all you need to start a cluster.

4.1 Starting

Distributed, but alone:

amtal@niflheim:$ erl -sname alpha -setcookie ez
(alpha@niflheim)1> node().
alpha@niflheim
(alpha@niflheim)2> nodes().
[]

Node names are just atoms – non-repeating @ and dots are accepted, though if dashes show up you’ll have to put the atom in quotes.

(alpha@niflheim)3> this.is@an.atom.
'this.is@an.atom'
(alpha@niflheim)4> 'this-is@an.atom'.
'this-is@an.atom'

4.2 Connecting

Ping a node to merge clusters with it. Cookies need to match – they’re less a security feature, and more a cluster partition feature.

amtal@niflheim:$ erl -sname beta -setcookie ez
(beta@niflheim)1> nodes().
[]
(beta@niflheim)2> net_adm:ping(epsilon@niflheim).
pang
(beta@niflheim)3> net_adm:ping(alpha@niflheim).
pong
(beta@niflheim)4> nodes().
[alpha@niflheim]
(beta@niflheim)5> 

Some documentation entry points: net_adm, erlang:monitor_node/2. The spawn functions and various OTP processes all have optional parameters for dealing with nodes.

5 Job Control and Remote Shells

Finally, we get to the feature that really bumps erl into OS shell category. After the overview of distribution, hitting Ctrl+G, then h, then enter should tell you all you need to start.

(gamma@niflheim)1> 
User switch command
 --> r alpha@niflheim
 --> j
   1  {shell,start,[init]}
   2* {alpha@niflheim,shell,start,[]}
 --> c 2
Eshell V5.8.1  (abort with ^G)
(alpha@niflheim)1> 

This lets you ‘telnet’ into any node you can reach from your machine, purely within Erlang. The connection is unencrypted, and the cookie is only there to keep separate clusters separate, but on a private network this opens many possibilities.

6 Summary

Think operating system. Multiple users in multiple sessions, doing different things on the same node, in parallel and isolated from each other. Utilities for debugging, testing, updating, and monitoring systems both local and remote, all inside the erl shell.

The mark of a good language is the infectious tendency to make you wish other languages had its features. In the right fields, Erlang excells at this.

Yes, it reinvents the wheel, requires training in a totally new environment, and makes a large suite of sysadmin monitoring tools useless. Trust me, it’s worth it. Your return on investment is transparently distributed, extremely lightweight parallel code that can be upgraded while running and can be built to deal with the most byzantine failures.