Bits of Erlang, chapter 1: Concurrency

Use the UP and DOWN keys on your keyboard to reveal and hide answers.
The N(ext) and P(revious) keys will also work.

Press Escape to reveal everything and disable interactivity.

1

Do you have the interactive Erlang, shell, erl, ready to go?

Let me try:

$ erl
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4]

Eshell V6.4  (abort with ^G)
1>

Yes!

2

Press Control-C twice to get out.

Ok.

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
^C$
3

Great! We’re ready to begin.

Start the shell again, and let’s define a simple function. What do you think it does?

> Id = fun Id(X) ->
         X
       end.

It looks like this function just returns the parameter that was passed.

4

Yes. Does it seem strange to you that variable names are Uppercase?

No, please continue.

5

What happens when we pass this function a number, say, 22?

> Id(22).

We should get back 22.

6

Indeed:

> Id(22).
22

As expected.

7

Id is a rather trivial function, but productively so.

Can you think of a simple function that’s of no use? What would it return?

Nothing!

8

Yes, that’s a good choice.

> Forever = fun Forever() ->
              Forever()
            end.

Let’s run it.

> Forever().

… I’m stuck now.

9

As expected. This function will never return, as it just calls itself over and over again.

Quit the erl shell and let’s try again.


If you’re worried about the stack, don’t be. Erlang does tail call optimization.

I’m back again, in a new shell…

10

Erlang lets us create little pocket universes, where we can run our functions.

Try this:

> Forever = fun Forever() ->
              Forever()
            end.

> spawn(fun() -> 
          Forever()
        end).
<0.36.0>

Hmm… I got back a strange number. I can still use my shell, though!

11

That is the address to our pocket universe.

How can I be sure?

12
> ForeverPid = spawn(fun() ->
                       Forever()
                     end).

> is_pid(ForeverPid).
true
13

That’s the ID (pid) of the pocket universe in which our function is running.

Erlang calls this pocket universe a process.

What do you think this returns?

> is_process_alive(ForeverPid).
true
14

What if we run our Id function in a pocket universe?

> IdPid = spawn(fun() -> Id(22) end).
<0.44.0>

Looks like it worked.

15

But is that universe still alive?

> is_process_alive(IdPid).
false
16

Is ForeverPid still alive?

> is_process_alive(ForeverPid).
true

Yes, it seems to be.

17

What’s the difference between the Id function and the Forever function?

Id returns with a value, but Forever just keeps on running.

18

Yes, the pocket universe stays alive as long as the function it was started with keeps running.

How long will this pocket universe remain alive?

> SleepyPid = spawn(fun() -> 
                      timer:sleep(timer:seconds(15)) 
                    end).

15 seconds, if the function names can be trusted.

> is_process_alive(SleepyPid).
true
> timer:sleep(timer:seconds(15)).
ok
> is_process_alive(SleepyPid).
false
19

Let’s print something to the screen.

Will this do?

> Print = fun Print(Thing) ->
            erlang:display(Thing)
          end.

If the names of things can be trusted, then yes, it’ll do.

20

Can you try it out?

> Print("hello, world!").
"hello, world!"
true
21

Can you explain that?

"hello, world!" was printed as expected, and then true was printed.

22

Can you elaborate?

Can you give me a hint?

23

Try to find out how erlang:display() works.

What happens when you run

> erlang:display(true).
true
true
24

And

> erlang:display(false).
false
true
25

One more thing…

> Result = erlang:display("something else").

What is the value of Result?

> Result.
true
26

Can you explain this behaviour now?

erlang:display() displays its parameter on the screen, and then returns true.

The Erlang shell erl prints the last returned value, so we see true printed out.

27

Exactly.

In that case, what will happen when we run this code?

> V = spawn(fun() -> 
              Print("hello, pocket universe!")
            end).
"hello, pocket universe!"
<0.46.0>
28

And what will V be equal to?

> V.
<0.46.0>

The Pocket-universe ID of our freshly spawned universe.

29

Will this universe be alive?

No.

30

Can you check?

> is_process_alive(V).
false

As expected.

31

What do you think this function will do?

> PrintForever = fun PrintForever(X) ->
                   erlang:display(X),
                   PrintForever(X)
                 end.

It seems it will print something forever.

32

Would you like to run it?

Uh, here goes:

PrintForever("hello").
"hello"
"hello"
"hello"
"hello"
"hello"
"hello"
...
33

You seem to be stuck.

Yes. This is the same as the non-terminating function, but worse on the eyes.

34

Do you remember how to exit the shell?

Yes, using Ctrl-C Ctrl-C:

"hello"
"hello"
"hello"

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
$
35

Let’s turn down the volume. Open up the erl shell again and try the following:

> PrintForever = fun PrintForever(X, Interval) ->
                   erlang:display(X),
                   timer:sleep(timer:seconds(Interval)),    
                   PrintForever(X, Interval)
                 end.

What do you expect to happen when you run this function?

The same thing that just happened: the shell will get stuck,
but a new line of output will appear only every Interval seconds.

36

Would you like to run it?

I’d rather not have to quit the shell and re-open it again.

37

What if you ran it in its own pocket universe?

Let’s try it.

> spawn(fun() ->
          PrintForever("hello_pocket_universe", 10)
        end).
"hello_pocket_universe"
<0.36.0>
38

Wait 10 seconds more…

"hello_pocket_universe"
39

Okay, and can you use your shell? Try

> 2 + 2.
>2 + 2.
4
40

Now, let’s launch a bunch more universes!

This will make the shell unusable, eventually…

41

Yes, but it’ll be fun!

Okay, how would we launch many universes in a concise manner?

I don’t want to type too much.

42

Then we must take a quick detour through Erlang syntax.

Try the following code in the shell:

> [ {hello, N} || N <- [1,2,3,4,5] ].
> [ {hello, N} || N <- [1,2,3,4,5] ].
[{hello,1},
 {hello,2},
 {hello,3},
 {hello,4},
 {hello,5}]

Well, it worked. But I don’t know what it means.

43

Let’s break it down. hello is an atom. It’s like a string, but more lightweight. If an atom contains no spaces or special characters, you can write it without quotes.

If it does contain special characters, you will need to wrap the entire atom in single quotes. Underscores don’t count as special characters.

> hello.
hello

> 'hello world!'.
'hello world!'
> another_atom.
another_atom.

> 'email@address.com'.
'email@address.com'

Makes sense.

44

Those things surrounded by { and }, and separated by , are called tuples. Think of them as simply ad-hoc packages of data.

> {a, tuple, with, five, atoms}.
{a,tuple,with,five,atoms}

> {1,2,3,4,5}.
{1,2,3,4,5}

Okay.

45

Are these tuples?

> {}.
> {1,2,3,potato}.
> {1,2,3,potato, fun(X) -> X + 5 end}.

I think so. The last one looks like it contains a function.

> {1,2,3,potato, fun(X) -> X + 5 end}.
{1,2,3,potato,#Fun<erl_eval.6.50752066>}
46

Yes. Anything can be packed into a tuple, including functions and Pocket-universe Ids.

Now, lists look like tuples, except they’re surrounded by [ and ].

Is this a list?

> [1,2,3,4]

Yes, by your definition.

47

Verify your answer with the function is_list.

> is_list([1,2,3,4]).
true
48

Are all of these lists as well?

> [].
> [1].
> [1,potato, fun(X) -> X + 1 end].

By your definition, yes.

49

Correct. Lists are much like tuples on the surface, but we can perform many more interesting operations on lists.


See The Little Schemer by Friedman and Felleisen (MIT Press) for a thorough course on lists.

Okay, does this have anything to do with that line of code you’re explaining?

50

Yes. Let’s take a look at that line again.

> [ {hello, N} || N <- [1,2,3,4,5] ].

This is called a list comprehension. It is a concise way of creating a list.

Do you recognize the Erlang data structures?

It looks like there’s a list on the outside, a tuple, a double-pipe ||, a variable, a left-arrow <-, and another list on the inside.

51

Correct, when you see || and <-, you can assume you’re in the presence of a list comprehension.

Read || as for every and <- as in. Read all other data as-is.

The tuple {hello, N} for every N in the list [1,2,3,4,5].

52

Correct. Let’s do one more.

Read this, and describe what it returns.

> [{N, math:pow(2,N)} || N <- [1,2,3,4,5,6,7,8,9,10]].

The tuple {N, 2 to the power of N} for every N in between 1 and 10, inclusive.

This returns the first 10 powers of two, preceded by the exponent.

53

Don’t you wish there was a nicer way to describe the range of numbers we want to use as the source of N?

I do.

54

This will likely do what you expect:

> lists:seq(1, 10).
> lists:seq(1, 10).
[1,2,3,4,5,6,7,8,9,10]
55

Can you generate the first 100 powers of two?

> [ math:pow(2, N) || N <- lists:seq(1,100) ].
[2.0,4.0,8.0,16.0,32.0,64.0,128.0,256.0,512.0,1024.0,2048.0,
 4096.0,8192.0,16384.0,32768.0,65536.0,131072.0,262144.0,
 524288.0,1048576.0,2097152.0,4194304.0,8388608.0,16777216.0,
 33554432.0,67108864.0,134217728.0,268435456.0,536870912.0|...]
56

Now we can return to the task of spawning a whole swarm of pocket universes.

How many is reasonable?

57

A lot more than you expect. Let’s try ten thousand for starters.

What will we want running in our pocket universe?

58

Let’s revisit our PrintForever function.

> PrintForever = fun PrintForever(X, Interval) ->
                   erlang:display(X),
                   timer:sleep(timer:seconds(Interval)),    
                   PrintForever(X, Interval)
                 end.

Can you make each pocket universe print a different number?

Yes. Here goes.

> [ spawn(fun() -> PrintForever(N,3) end) 
    || N <- lists:seq(1,10*1000) ].
59

Wait!

Before you run this and inevitably fill your screen with numbers, can you read that list comprehension out loud?

Spawn a universe running the function PrintForever(N,3) for every N in the list 1..10000.

60

Let’s do it!

I see a lot on the screen.

61

Yes. All those pocket universes are running concurrently. Each universe performs its own work, completely oblivous to what the others are doing.

How many can I run, realistically?

62

Launch the Erlang shell with erl +P 134217727 and see if you can hit that limit.

You’ll want to spawn PrintForever with a higher interval, so that outputting to the Erlang shell doesn’t become a bottleneck.

Okay, I’ll experiment. See you next time!