Erlang - gregorymorrison/euler1 GitHub Wiki

Erlang, introduced in 1986, is a language that you should learn because they've solved the concurrency problem. Whereas in other languages like Java or, God forbid, Python, concurrency is difficult if not, well, very difficult, in Erlang it's a breeze. Erlang programs are constructed of a bunch of independent actors that pass messages between each other.

Now, since Euler1 is a trivial problem, it didn't require any usage of Erlang's facilities for concurrency. Here I just define a simple recursive function and call it:

% Euler1 in Erlang

-module (euler1).
-export ([euler/0]).

euler() ->io:format( "~w~n", [euler(999, 0)] ).

euler(0, Acc) -> Acc;
euler(X, Acc) when X rem 3 == 0; X rem 5 == 0 ->
	euler(X-1, Acc+X);
euler(X, Acc) -> euler(X-1, Acc).

Notice the Haskell-style pattern-matching? Function euler() is defined four times, three having the same datatypes. I've given all my functions the same name for illustration. Erlang distinguishes them by starting at the top and working its way down, executing the first signature that matches its arguments.

  • The first euler() with no arguments is the entry point, defined at the top.
  • The second euler() will match any method call where the first argument is 0.
  • The third and fourth definitions of euler() only differ by a guard - checking if X is divisible by 3 or 5. It took me about a day to write this simple example. Erlang doesn't have the greatest debugging facilities, and one issue that bit me hard is that variables must be uppercase (really, Erlang?)

But really, what is an exploration of Erlang without concurrency? I've rewritten Euler1 as a set of simple concurrent processes - euler, which provides the solution, and caller, the shell that gets the solution and outputs it:

  1. lines 7-8 - main starts up the processes caller and euler. main gets the processID of euler and passes it to caller. Notice that main doesn't get the processID of caller - it doesn't care because it actually does nothing after this point. Remember, this is decentralized code - no central coordination.
  2. line 16 - euler's first instruction is to listen, so it initially takes no action.
  3. lines 22-23 - caller messages euler, sending it an integer and its own process ID. caller then sits and listens.
  4. euler gets the message from caller and wakes up. Upon getting a message, it
  5. line 17 - gets the solution from euler1 (a simple recursive function, not a process).
  6. line 18 - messages caller, sending it the results
  7. line 19 - euler terminates.
  8. lines 23-24 - caller gets the message from euler and wakes up. Upon getting a message, it displays the results.
  9. line 25 - caller terminates.
1   % Euler1 in Erlang
2
3   -module(foo).
4   -export([main/0, caller/1, euler/0]).
5
6   main() ->
7       Euler_PID = spawn(foo, euler, []),
8       spawn(foo, caller, [Euler_PID]).
9
10  euler1(0, Acc) -> Acc;
11  euler1(X, Acc) when X rem 3 == 0; X rem 5 == 0 ->
12      euler1(X-1, Acc+X);
13  euler1(X, Acc) -> euler1(X-1, Acc).
14
15  euler() ->
16      receive {N, Caller_PID} ->
17          Result = euler1(N, 0),
18          Caller_PID ! {result, Result}
19      end.
20
21  caller(Euler_PID) ->
22      Euler_PID ! {999, self()},
23      receive {result, Result} ->
24          io:format("~p~n", [Result])
25      end.

I'll be honest - it took me days to write this simple example. I had to deconstruct a lot of Erlang HelloWorlds to figure out how this language works. Be prepared for a very steep learning curve. One of the things that really tripped me up is that a process can receive messages with multiple signatures. The arguments listed for the process are its constructor arguments, but a process is blocked at receive can accept a completely different set of arguments. Notice that line 7 calls euler's constructor with no arguments; then line 22 calls euler, blocked at line 16, with 2 arguments.

It also took me a while to figure out how to execute Erlang code. Erlang is usually run in an interactive shell; to simply execute a Hello World, you need to invoke and then terminate the shell. The following will execute the first version here, with the entry point being euler():

$ erl -compile euler1
$ erl -noshell -s euler1 euler -s init stop
233168

The following will execute the other version, with the entry point being main():

$ erl -compile euler1
$ erl -noshell -s euler1 main -s init stop
233168