Programming language and runtime environment, much like Java, developed by Ericsson for use in telecommunication systems. Noteworthy features include soft real-time capability, good fault tolerance, multithreading and easy distribution of the workload.

Both the development libraries (called OTP) and the runtime environment is open source.

Hello World in Erlang:

-module(hello).
-export([hello_world/0]).
	
hello_world() -> io:fwrite("hello, world\n").

Erlang is also the unit used for measuring workload over a communication link, where 1 is maximal utilization of the link.

I am going to demonstrate some simple programs in Erlang.

The above writeup does not mention that Erlang is a functional language. Erlang functions typically have the form of a chained series of statements, with the last statement being returned as the value of the function call. The "Hello, World" program is therefore atypical in that the value of the call is ignored; the output of "Hello, world" is a side effect of the call to io:format. Erlang code normally does not have side effects, but for I/O, such an effect is necessary.

Let's see how functional programming works in Erlang.

Suppose we want to write a function that, given the lengths of two legs of a right triangle, will return the length of the hypotenuse of the triangle. (This is just the Pythagorean Theorem.) We can write such a function like this. (The -module and -export statements are necessary for the Erlang interpreter to load the module correctly.)

-module(hypo).
-export([hypotenuse/2]).

hypotenuse(A, B) -> 
    math:sqrt(A * A + B * B).

If we save this code to a file hypo.erl, we can load it as a module into the Erlang interactive shell and try it out:

Erlang (BEAM) emulator version 5.4.9 [threads:0]

Eshell V5.4.9  (abort with ^G)
1> c(hypo).
{ok,hypo}
2> hypo:hypotenuse(5, 6).
7.81025
3> hypo:hypotenuse(9, 4).
9.84886
4> hypo:hypotenuse(7, 24).
25.0000
5> 

Our hypotenuse function is short and tidy. Suppose, however, that now we want to write a function to solve a quadratic equation Ax^2 + Bx + C = 0 (where the roots are real). (See "quadratic formula" if you don't know it by heart.) The first thing to notice is that a quadratic equation will (potentially) have two solutions. We should therefore return a list, not a single value. The following function is a first try:

-module(quadratic).
-export([solve/3]).

solve(A, B, C) ->
    [(-B + math:sqrt(B * B - 4 * A * C)) / (2 * A), (-B - math:sqrt(B * B - 4 * A * C)) / (2 * A)].

Now if we try this, we see it works for real solutions. The second call fails because the solutions are imaginary.

Eshell V5.4.9  (abort with ^G)
1> c(quadratic).
{ok,quadratic}
2> quadratic:solve(1, 4, 3).
[-1.00000,-3.00000]
3> quadratic:solve(1, 3, 16).

=ERROR REPORT==== 3-May-2006::00:35:34 ===
Error in process <0.30.0> with exit value: {badarith,[math,sqrt,[-55]},{quadrat
ic,solve,3},{erl_eval,do_apply,5},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {badarith,[{math,sqrt,[-55]},
                      {quadratic,solve,3},
                      {erl_eval,do_apply,5},
                      {shell,exprs,6},
                      {shell,eval_loop,3}]} **
4> 

Erlang tells us here that it was asked to take the square root of -55, which it cannot. Let's not worry about these errors. Our function works well for equations with real solutions. Our definition is repetitive and clunky, though. We can shorten it like this:

-module(quadratic).
-export([solve/3]).

solve(A, B, C) ->
    Discriminant = B * B - 4 * A * C,
    SqrtDiscrim = math:sqrt(Discriminant),
    [(-B + SqrtDiscrim) / (2 * A), (-B - SqrtDiscrim) / (2 * A)].

Here, Discriminant and SqrtDiscrim are temporary variables, local to the solve function. The value of a variable cannot be changed. The final element of the chain, [(-B + SqrtDiscrim) / (2 * A), (-B - SqrtDiscrim) / (2 * A)], is the returned value of the function.

Our solve function creates a list for its return value. Lists are fundamental to Erlang. They are created by enclosing contents in brackets ([]). Most functions operating on lists decompose them in the following manner:

apply_function_to_list_elements(Function, [Head | Tail]) ->
    [Function(Head) | apply_function_to_list_elements(Function, Tail)];
    
apply_function_to_list_elements(Function, []) -> [].

Here, Head becomes the first element of the list passed as the second parameter, and Tail becomes the rest. This is like car and cdr in Lisp. The second function clause is invoked when the list is empty; it has no first element in that case.

If, for example, we have a function Square that squares its argument, calling

apply_function_to_list_elements(Square, [3, 4, 5])

will yield [9, 16, 25].

As we can see, defining a function with multiple clauses is easy. We can also specify guards on the parameter values. We can define the factorial function with two clauses:

factorial(0) -> 1;
factorial(N) when N > 0 -> N * factorial(N - 1).

Which clause is invoked depends on the value of N passed to the factorial function. If a negative value is passed, an error will result, since neither clause fits the invocation for a negative N. The second clause is executed only for N > 0; the phrase "when N > 0" is called a guard.

In addition to lists, Erlang primitives include atoms, tuples, and processes. Atoms begin with a lowercase letter (ok, error, bob). Atoms are used where an enumeration might be used in C or Java. For instance, C code dealing with a deck of cards might define the suits thus:

enum suit {
    SPADES, HEARTS, DIAMONDS, CLUBS
};

In Erlang, we would just use the atoms: spades, hearts, diamonds, clubs. Many Erlang functions return the atom <ok on success, instead of a numeric code.

Tuples are written with braces and are akin to C structs: {4, "hello", bob} is a 3-tuple containing the integer 4, the string "hello", and the atom bob. Tuples, like lists (and like every data type in Erlang), are immutable.

Concurrency is where Erlang shines: processes are built into the language, and it is easy for processes to communicate with one another. Imagine that process 1 has process id Pid1, and process 2 has process id Pid2. Then Pid1 can signal Pid2 by sending it any Erlang value. In code it is written: Pid2 ! Value. Similarly, Pid2 can send Pid1 a value with Pid1 ! Value. A process waits for messages and then acts on each one received. The Erlang receive construct implements listening for events:

function() ->
    receive
        keepgoing ->
            io:format("I'll keep going!~n", []),
            function();
        stop ->
            io:format("I'll stop.~n", [])
    end.

Suppose we create a process Pro that invokes function. We can send Pro the atom keepgoing, and it will print its inspirational message and resume listening; or we can send Pro the atom stop, and it will stop. In the former case, the listening continues because the value of the first function() call is the value of the receive block, which becomes the value of the inner function() call, etc. Erlang optimizes tail-recursive calls like this, though, so that the code is efficient, and not infinitely recursive.

I find Erlang's syntax to be very natural throughout, and the interprocess communication works very simply; I just started using Erlang processes two hours ago and have made significant headway in my program already. Try Erlang for yourself!

This is all gleaned from the docs and examples at erlang.org

Log in or register to write something here or to contact authors.