Jump to content

Erlang Programming/Printable version

From Wikibooks, open books for an open world


Erlang Programming

The current, editable version of this book is available in Wikibooks, the open-content textbooks collection, at
https://en.wikibooks.org/wiki/Erlang_Programming

Permission is granted to copy, distribute, and/or modify this document under the terms of the Creative Commons Attribution-ShareAlike 3.0 License.

History

History

[edit | edit source]

The Erlang language was first written in Prolog by Joe Armstrong.[1] Joe is a known fan of Prolog and borrowed much syntax from Prolog in the design of Erlang. This first Prolog version of Erlang was slow and motivated the creation of a virtual machine. Later an emulator called the BEAM (Bogdan's Erlang Abstract Machine) was written in C and is about 200,000 lines of code. It is thought that Erlang is both a reference to mathematician Agner Krarup Erlang and short for "Ericsson Language", due to its place of birth. [2]

References

[edit | edit source]
  1. Audio interview with Joe Armstrong from (http://www.se-radio.net)
  2. Mailing list discussion, 1999 (https://erlang.org/pipermail/erlang-questions/1999-February/000098.html)


Overview

Everything is a process

[edit | edit source]

Software technologies often use a particular organizing principle. In Linux, everything is a file. In Ruby, everything is an object. In lisp, everything (program or data) is a list. In Erlang, the most important unit of organization is the process. Each process has an ID and at least one function that starts it (an entry point). In theory, there could be multiple entry point functions for a process. Each process is not limited to one function when running. On the contrary, one can have a whole barrel-of-functions that recursively call one-another and behave together as one process. GS based graphics programs in Erlang often use the barrel-of-functions per process technique to handle GUI events. CPU time is scheduled in a round robin fashion with equal time slices to each process.

Messages

[edit | edit source]

Processes send messages and receive messages from one another. Messages are read with pattern matching. The messages are matched in a fifo (first in, first out) way.

Be parallel safe

[edit | edit source]

To be serial-safe we need to control side effects. As a solution, first we invented structured programming, then object oriented programming. Each time the goal was to isolate state and variables to reduce side-effects. In the same tradition, side effects are more restricted in Erlang. If a function has no state then it will behave the same way every time. If a function has no side effects, then it cannot cause other functions to break.

A pure function is a function that returns the same value given the same arguments regardless of the context of the call of the function. This is what we normally expect from a mathematical function. A function that is not pure is said to have side effects.

Side effects typically occur if a function
a) sends a message
b) receives a message
c) calls exit
d) calls any BIF* which changes a processes environment or mode of operation (e.g. get/1, put/2, erase/1, process_flag/2 etc).

—quoted from Erlang Programming Rules (see references)

 * Note: "BIF" stands for "built in function"

Forget state

[edit | edit source]

In general, state is forgotten in Erlang functions. Iteration is simulated by recursion. Variables can only be assigned once during each function call. State is carried by function arguments or state is put into a database if absolutely necessary. Avoiding state protects the integrity of processing in parallel. Each function call can be thought of as the creation of a pocket universe that has a set of equations that are true for the extent of invocation. Alternatively, one could conceive of erlang as a system to program with a kind of temporal logic. Some say that Erlang does not even have variable assignment, it only has pattern matching.

[ Head | Tail ] = [ 1, 2, 3 ], Head = 1.
 ok

Head must always match in a consistent way or the containing process will fail.

Head = 2.
 Error in process <0.29.0> with exit value: {{badmatch,2},[{erl_eval,expr,3}]}

Pattern matching can be thought of as a series of assertions that must be true.

Mixing paradigms

[edit | edit source]

Simple object oriented programming can be simulated with erlang by using processes for objects and messages for methods.

The new software crisis

[edit | edit source]

In 2003 Moore's law took a sharp turn. Overheating caused microprocessors to limit their speed to 4 GHz. The solution was to create multicore processors. This allowed Moore's law to continue but only in a parallel way. Multicell computing created a new software crisis, the MIMD (Multiple Instruction Multiple Data) software crisis. SIMD (Single Instruction Multiple Data) is relatively easy, MIMD is difficult. (Ref:The free lunch is over). MIMD programs are needed to make use of the new multi-core computers, otherwise, Moore's law breaks.

Uses of Erlang

[edit | edit source]

Erlang is well suited to address the new MIMD software crisis. Erlang can be used for agent programming because of parallel processes and message passing. Erlang can be used for classical AI programming because it is a symbolic language. Because of extreme process isolation it should work well for genetic/evolutionary programming.

References

[edit | edit source]

[1] - The free lunch is over, Dr Dobbs Journal, March 2005. (html)

[2] - Rules of Erlang programming from erlang.se (html)

[3] - Joe Armstrong (2003). "Making reliable distributed systems in the presence of software errors". Ph.D. Dissertation. (pdf)


Getting Started

← Overview | Terms →

Getting Erlang

[edit | edit source]

Erlang/OTP is available as free software from Open Source Erlang Downloads. Once you have downloaded the Erlang/OTP distribution, you will need to compile it (if on a Unix platform) and install it.

Installing Erlang

[edit | edit source]

In addition to the brief instructions below, you may wish to consult the Installation Guide.

Unpack the source distribution. Decide where you want Erlang and its libraries installed. For our example, we will assume you want it in /usr/local/erlang/otp_r11b. Create this directory, as well as a build directory for building the Erlang sources.

mkdir /usr/local/erlang/otp_r11b

From within the top-level directory, type ./Install /usr/local/erlang/otp_r11b. This will configure, build and install the Erlang/OTP distribution.

Add the distribution's bin directory to your path. You may wish to set this in your shell initialization file.

Microsoft Windows

[edit | edit source]

The distribution is a self-installing .exe file. Invoke it (for example, by double-clicking on its icon) and answer the prompts.

The Erlang Shell

[edit | edit source]

Erlang comes with a shell which is used interactively when controlling the Erlang environment and developing programs. On Unix, invoke the shell by typing erl on the command line. On Windows, type werl on the command line, or double-click the Erlang shortcut icon.

This starts the Erlang system and provides a shell for input and evaluation of Erlang.

Eshell V5.4.13 (abort with ^G)
1>

Your First Erlang Code

[edit | edit source]

At the shell prompt, type "hello, world!" followed by a dot (.). You can follow that up with some arithmetic. Or answer the question "What is six times 9?" in base-13 notation:

1> "hello, world!".
"hello, world!"
2> 1 + 2.
3
3> 13#6 * 13#9.
54

When you are ready to exit, you can stop the Erlang system using the halt() built-in function:

4> halt().


Quick tips

One thing that can slow you down when you are getting started or trying to do some rapid prototyping is to export functions all the time. Rather, you can use the compiler directive. This makes programming erlang much more enjoyable, not to have to manually type:

 export(function_not_an_other_one/4).

everytime you add a function and would like to test it. Rather, you can use:

 compile(export_all).

You can manually export only the things you need to when you are done with the program.

Another handy trick is to use

 42> make:all([load]).

Rather than compile the latest version all the time.

 43> c(my_module_v17).

or is it

 44> c(my_module_v18).

?Module in code gives the current module name, which is super helpful when spawning.

 spawn(?Module, fun_name, arity),

Otherwise, you need to change the module name in every spawn every time you change the name of the module. If you would like to keep an eye on a process you can monitor it, after you have registered it.

 45> register(my_loop, spawn(my_module, my_loop, [])).
 46> Mon_my_loop = erlang:monitor(process, my_loop).

A helpful utility function is rpc.

 rpc(Dest, Msg) ->
   To ! {self(), Msg},
   receive
     {Dest, Answer} -> Answer
   after 1000 ->
     {Dest, did_not_answer_msg, Msg}
   end.

This helps keep synchronous commands between processes from getting answers from the wrong process. We force the identification of the process providing the answer, and sequential queuing should handle the rest.


Terms

← Getting Started | Pattern Matching →

The basic unit of expressing a value in Erlang is the term. Terms are comprised of one of Erlang's simple or complex types.

Integers

[edit | edit source]

Integer constants are written as numbers, optionally prefixed with a base and the octothorpe (#):

 1> 2.
 2
 2> 16#ff.
 255
 3> 2#1011.
 11

Erlang uses arbitrary-precision integers, which support integers with any number of digits.

Erlang also provides another syntactic shortcut: you can write an integer as the dollar sign ($) and a character, and the value is the ASCII value of that character. This is of use when handling strings, as strings are typically represented in Erlang as lists of integers (see Strings, below).

Floats

[edit | edit source]

Floats (floating-point numbers) are written as numbers with decimal places, optionally with an exponent separated from the number with the letter e.

 4> 1.2.
 1.20000
 5> 1.0.
 1.00000
 6> 2.0e-4.
 2.00000e-4

Atoms

[edit | edit source]

Atoms are named constants. Atoms begin with a lower-case letter and can contain letters, digits and the underscore character (_); or they are quoted with single quotes (').

 1> ok.
 ok
 2> 'OKAY'.
 'OKAY'
 3> this_is_an_atom.
 this_is_an_atom

The special atoms true and false represent boolean values.

Atoms are often used as the keys in key-value pairs, indicators of success and failure (e.g. ok and error) and to identify parts of a complicated structure for Pattern Matching.

Tuples

[edit | edit source]

Tuples are terms composed of multiple values, and are of fixed length. Tuples are surrounded by braces ({ and }), and the elements of the tuple (any Erlang term, including tuples) are separated by commas.

 1> {ok, 9}.
 {ok,9}
 2> {true, {127, 0, 0, 1}}.
 {true,{127,0,0,1}}
 3> {box, {width, 10}, {height, 35}}.
 {box, {width, 10}, {height, 35}}

The 3rd example shows something called a tagged tuple. Where possible use tagged tuples as they make it clear what the tuple is supposed to represent. Later on we will look at records which is just a shorthand for tagged tuples.

Lists

[edit | edit source]

Lists are terms composed of multiple values, of varying length. Lists are surrounded by brackets ([ and ]) and each member of the list (any Erlang term, including lists) separated with commas.

Lists can also be composed of a head and tail portion, separated by the vertical bar character (|): [Head|Tail]. The Tail can be any valid Erlang term, but is usually a list representing the members of the list after the head.

 1> [one, two, three].
 [one,two,three]
 2> [1, 2|[3, 4, 5]].
 [1,2,3,4,5]
 3> [{key1, value1}, {key2, value2}].
 [{key1,value1},{key2,value2}]

Lists in which the smallest possible tail is the empty list ([]) are known as well-formed lists.

Strings

[edit | edit source]

Erlang has no separate string type. Strings are usually represented by lists of integers (and the string module of the standard library manipulates such lists). Each integer represents the ASCII (or other character set encoding) value of the character in the string. For convenience, a string of characters enclosed in double quotes (") is equivalent to a list of the numerical values of those characters.

 1> "one".
 "one"
 2> [$o, $n, $e].
 "one"
 3> $o.
 111
 4> $n.
 110
 5> $e.
 101
 6> [111, 110, 101].
 "one"

The Erlang shell "guesses" whether a given list is a printable string and prints it that way for convenience.

A Pid or process id is a special type in Erlang. To see what one looks like you can get the pid of yourself with self().

1> self().
  <0.29.0>

A ref (or reference) is a term which is unique, even across Erlang nodes. You can create a new ref by calling erlang:make_ref(). A reference is only used as a unique tag or identifier. An Erlang reference should not be confused with a reference in C/C++.

2> erlang:make_ref().
#Ref<0.0.0.41>

Other types

[edit | edit source]

Erlang has other types, such as funs (closures), bit-strings, binaries (continuous blocks of arbitrary data) and ports (port identifiers), that will be covered in their appropriate sections.


Pattern Matching

← Terms | Expressions →

Erlang uses powerful pattern-matching to bind variables to values. Pattern matching can be explicit, as when the = (pattern matching operator) is used, or implicit, as when a function call is processed and a function's actual arguments are matched with its formal parameters.

Patterns

[edit | edit source]

Patterns look the same as terms - they can be simple literals like atoms and numbers, compound like tuples and lists, or a mixture of both. They can also contain variables, which are alphanumeric strings that begin with a capital letter or underscore. A special "anonymous variable", _ (the underscore) is used when you don't care about the value to be matched, and won't be using it.

A pattern matches if it has the same "shape" as the term being matched, and atoms encountered are the same. For example, the following matches succeed:[1]

  • A = 1.
  • 1 = 1.
  • {ok, A} = {ok, 42}.
  • [H|T] = [1, 2, 3].

Note that in the fourth example, the pipe (|) signifying the head and tail of the list as described in Terms.

These matches fail:

  • 1 = 2.
  • {ok, A} = {failure, "Don't know the question"}.
  • [H|T] = [].

In the case of the pattern-matching operator, a failure generates an error and the process exits. How this can be trapped and handled is covered in Errors.

Patterns are used to select which clause of a function will be executed (this is covered in Functions; which option to select in a case expression (Expressions); and which messages to retrieve from the mailbox (Processes).

Variables

[edit | edit source]

Erlang variables are single-assignment variables that do not have to be declared. They are written with a capital letter or underscore followed by an alphanumeric sequence. They are bound to values using the pattern matching mechanism. The Erlang compiler will produce an error if an unbound variable is used, and a warning if a bound variable is not used. Sometimes you might encounter a _Var variable. This variable is bound and does contain a value, however it suppresses compiler warnings regarding unused bound variables.

Notes

[edit | edit source]
  1. Since Erlang variables are immutable, consider examples like this to be standalone--even though the A is used twice, it's a different A each time!


Expressions

← Pattern Matching | Functions →

Expressions

[edit | edit source]

Erlang statements look a little like sentences. One statement is a series of comma separated expressions ending with a period. Erlang expressions can either be ignored, stored, or returned depending on their position and structure in a statement.

4+3, H=6-2, lists:reverse([3,4,5]).
  [5,4,3]

In this Erlang example the expression: 4+3 is computed, the expression H=6-2 is computed, and the reverse of the list [3,4,5] is computed and returned. The results of 4+3 are ignored and the pattern 4 is matched to the variable H. For ever after H will have the unchangeable value 4. "lists" is the name of a standard module (library) that provides list utility functions. The result shown from the above expression is the value of the last statement so the repl will only show [5, 4, 3] as indicated above.

Problems:

1) Write an expression that matches the pattern H2 to the reverse of the list [{1,2},{2,1}].

2) Write an expression that matches the pattern H3 to the length of the list [{1,2},{2,1}].

3) Write an expression that matches the pattern H4 to the length of the flattened version of the list [{1,2},{2,1}].


Functions

← Expressions | guards →

Erlang Functions

[edit | edit source]

To see a function in erlang we can create the file: even_prime.erl with the following code.

-module(even_prime).                           % 1
-export([is_even_prime/1]).                    % 2
                                               % 3 
is_even_prime(2) ->                            % 4   clause 1 is simple
    true;                                      % 5
is_even_prime(N) when is_integer(N) ->         % 6   clause 2 has a guard: is_integer(N)
    false;                                     % 7
is_even_prime(Any) ->                          % 8   clause 3 is simple
    'I prefer integer inputs'.                 % 9

The function clauses are put in the order that they are checked. First is_even_prime(2) is checked for a match. If the argument matches then true is returned. This clause ends in a semicolon because the function is not finished being defined. If is_even_prime(2) fails to match then is_even_prime(N) is tried. is_even_prime(N) is checked for a match. N is a variable that matches any integer. The statement when is_integer is a guard that admits only integer types to N. The semicolon says we have more to go. The period at the end tells us that the function is finished being defined. is_even_prime(Any) matches anything of any type and returns the value 'I prefer integer inputs'. The function is now finished. This function is a total function and should cover all possible single argument inputs.

outputs:

 2> c(even_prime). 
./even_prime.erl:8: Warning: variable 'Any' is unused
{ok,even_prime}
 3> even_prime:is_even_prime(2). 
true
 4> even_prime:is_even_prime(1). 
false
 5> even_prime:is_even_prime(seven). 
'I prefer integer inputs'
==================================================================
Syntax/structure of a function: 
==================================================================
semicolon     - ends a clause
period        - ends a function 
when          - starts a guard
arrow         - separates the head from the tail of the function
function head - input part of function includes the signature and guard
function tail - output/consequence(s) part of function
signature     - the function name and argument structure/count
==================================================================
rotate_list( [H|T] ) when is_atom(H) -> T ++ [H].
                                     .
[----signature-----] [----guard----] .  
                                     .                     
[-----------function head----------] .  [--function tail--]
                           
==================================================================


guards

← Functions | Modules →

Erlang Guards

[edit | edit source]

Guard structures

[edit | edit source]

Legal guards in Erlang are boolean functions placed after the key word, "when" and before the arrow, "->". Guards may appear as part of a function definition or in "receive", 'if', "case", and "try/catch" expressions.

We can use a guard in a function definition
Example program: guardian.erl

-module(guardian).
-compile(export_all).
   
the_answer_is(N) when N =:= 42 -> true;
the_answer_is(N) -> false.
   
% ============================================= >% 
%
% Example output:
%
% c(guardian).
% ok
%
% guardian:the_answer_is(42).
% true
%
% guardian:the_answer_is(21).
% false

and Fun definition

 F = fun
   (N) when N =:= 42 -> true;
   (N) -> false
 end.

receive expression

 receive
   {answer, N} when N =:= 42 -> true;
   {answer, N} -> false
 end.

if expression

 if 
   N =:= 42 -> true;
   true -> false
 end.

case expression

 case L of
   {answer, N} when N =:= 42 -> true;
   _ -> false
 end.

and try/catch

 try find(L) of
    {answer, N} when N =:= 42 -> true;
    _ -> false
 catch
    {notanumber, R} when is_list(R) -> alist;
    {notanumber, R} when is_float(R) -> afloat
    _ -> noidea
 end.

You will notice that in these examples it would be clearer (in real code) to remove the guard and modify the pattern matching instead.

Literate programming note: Anonymous match variables that start with an underscore like "_" are not generally recommended. Rather, it is nice to use some descriptive variable name like "_AnyNode". On the other hand, for tutorial code like this, a descriptive variable is more distracting than helpful.

 case L of
   {node, N} when N =:= 42 -> true;
   _AnyNode -> false
 end.

Multiple guards

[edit | edit source]

It is possible to use multiple guards within the same function definition or expression. When using multiple guards, a semicolon, ";", signifies a boolean "OR", while a comma, ",", signifies boolean "AND".

the_answer_is(N) when N == 42, is_integer(N) -> true;
geq_1_or_leq_2(N) when N >= 1; N =< 2 -> true;

Guard functions

[edit | edit source]

There are several built-in-functions (BIFs) which may be used in a guard. Basically we are limited to checking the type with, is_type(A) and the length of some types with, type_size() or length(L) for a list length.

is_alive/0                     
is_boolean/1                    
is_builtin/3                    
is_constant/1                   
is_float/1                      
is_function/2      is_function(Z, Arity)               
is_function/1                  
is_integer/1                    
is_list/1                       
is_number/1                     
is_pid/1                        
is_port/1                      
is_record/3                    
is_record/2                     
is_reference/1                 
is_tuple/1      

tuple_size/1
is_binary/1
is_bitstring/1
bit_size/1
byte_size/1        
length(Z) > N 
map_size(M)
A > B
A < B
A == B
A =< B
A >= B
A /= B
A =:= B     exactly equal
A =/= B     exactly not equal

Note: all erlang data types have a natural sort order.

atom < reference < port < pid < tuple < list ...


Modules

Erlang modules

[edit | edit source]

Each Erlang Programming source file

utility.erl

is required to be a separate module. Modules are created with the module statement.

-module(utility).         % 1
-export([rotate/1]).      % 2
                          % 3
rotate([H|T]) ->          % 4
    T ++ [H].             % 5

compile with

    c(utility).

run with

    utility:rotate([1,2,3]).

and get

    [2,3,1].

"utility" is the module created by the file utility.erl

utility functions like rotate can be imported elsewhere with:

    -import(utility).

so now we do not need to use the "utility:" prefix. Importing modules is not generally recommended. This is very similar to python and Java imports.


Errors

Errors

[edit | edit source]

We can deal with errors with throw and catch. In this example the value of an argument causes an error which causes an exception to be thrown. The function g() is happy only when the argument is greater than 12. If the argument is less than 13 then an exception is thrown. We try to call g() in start(). If we run into trouble then the exception is caught in the "case catch" structure inside of start().

Sample program listing:

-module(catch_it).
-compile(export_all).
                                                                 %
% An example of throw and catch
                                                                 %
g(X) when X >= 13 ->
   ok;
g(X) when X < 13 ->
   throw({exception1, bad_number}).
                                                                 %
% Throw in g/1 
% Catch in start/1
                                                                 %
start(Input) ->
   case catch g(Input) of
       {exception1, Why} ->
          io:format("trouble is ~w ", [ Why ]);
       NormalReturnValue ->
          io:format("good input ~w ", [ NormalReturnValue ] )
   end.
                                                                 %
%============================================================== >%   
% sample output:
                                                                 %
8> c(catch_it).    
{ok,catch_it}
                                                                 %
9> catch_it:start(12).
trouble is bad_number ok
                                                                 %
10> catch_it:start(13).
good input ok ok


Operators

Logical operators

not, and, or

Binary operators

bnot, bor, band, bxor

String operators

++, --, (\s*)
where (\s*) is the regular expression for white space which concatenates two strings

Disjunction Guards

f(X) when X==42 ; X==32 ; X==0 ->
             X+1.


Processes

Erlang Processes and Messages

[edit | edit source]

Processes are easy to create and control in Erlang.

The program chain_hello.erl builds a chain of processes as long as you like. Each process creates one process then sends a message to it. The program creates a chain of N processes which each print out "hello world!<N>" (where <N> is some integer).

Processes send messages to and receive messages from one another. Messages are read with pattern matching. The messages are matched in a fifo (first in, first out) way.

Note 1: The order of the final output depends on process scheduling.

Note 2: Time flows downward (in each vertical line, see note 1).

This is a Process Message Diagram for the execution of: chain_hello:start(1).

start(1)
   |
spawns -----------> listen(1)
   |                   |
   |                spawns --------------------> listen(0)
   |                   |                            |
   |                sends ----> speak ----------> prints --> "hello world 0"
   |                   |                            |
 sends --> speak --> prints --> "hello world 1"     |
                       |                            |
                       ok                           ok

Program listing for: chain_hello.erl

-module(chain_hello). 
-compile(export_all).
                                                            %
start(N)->                                                  % startup
       Pid1 = spawn(chain_hello, listen, [N]),
       Pid1 ! speak,
       io:format("done \n").
                                                            %
listen(0)->                                                 % base case
       receive
                speak ->
                       io:format("hello world!~w\n", [0])
       end;
listen(N)->                                                 % recursive case
       Pid2 = spawn(chain_hello, listen, [N-1]),
       Pid2 ! speak,
       receive
               speak ->
                       io:format("hello world!~w\n", [N])
       end.
% ---- sample output ---- %
%
% 14> chain_hello:start(4).
% done
% hello world!4
% hello world!3
% hello world!2
% okhello world!1
% hello world!0


Timeouts

Timeouts

[edit | edit source]

Timeouts are created by the [ receive - after - end ] structure. Timeouts are measured in miliseconds so 4000 = 4 seconds. We can create a simple timer with the following program: myTimer.erl

% ============================================================ >%
-module( myTimer ).
-compile( export_all ).
                                                                %
% A simple timer that uses a timeout.
                                                                %
start( Timeout ) ->
    receive
    after Timeout ->
        io:format( "your ~w secs are up." , [Timeout/1000] )
    end.	
% ============================================================ >%
%
% Sample output:
% 
% 8> c(myTimer).
% ok.
%
% 9> myTimer:start(4000).
% your 4.00000 secs are up.ok

Timeout values may be any number greater than or equal to 0, including the atom 'infinity'.


Macros

Macros

[edit | edit source]
-define(LIKERT_SCALE, lists:seq(1, 5)). 

A = ?LIKERT_SCALE.

The code makes A = [1,2,3,4,5].

Some handy predefined macros include:

 ?MODULE  (module name)
 ?LINE    (line number)
 ?FILE    (filename as a string)
 % ===========================================
 % Example code
 % ===========================================
 -module(test_macros).
 -define(LIKERT_SCALE, lists:seq(1, 5)).
 -compile(export_all).
 
 start() ->
   io:format("likert scale:  ~w \n", [?LIKERT_SCALE]),
   io:format("module name:   ~w \n", [?MODULE]),
   io:format("line number:   ~w \n", [?LINE]),
   io:format("filename:      ~s \n", [?FILE]),
   ok.
 % ===========================================
 % Example output
 % ===========================================
 %
 % 6> c(test_macros).
 % {ok,test_macros}
 % 7> test_macros:start().
 % likert scale:  [1,2,3,4,5] 
 % module name:   test_macros 
 % line number:   10 
 % filename:      ./test_macros.erl
 % ok


Techniques of Recursion

Techniques of Recursion

[edit | edit source]

Simple techniques

[edit | edit source]

Assembly-Disassembly

[edit | edit source]

Often we would like to build a list using a recursive function. Perhaps we would like to reverse a list. We start with a single-argument version (the public entry point function) and use it to call the double-argument version (private), where the extra argument contains the output we wish to build. We can take the head off of the input, [H|T], as we build up the output, Build. When input is empty, we are done.

Observe the nice way that strings are actually processed as lists of chars. Syntactic sugar changes strings into lists and back.

(Here we use the assembly-disassembly technique of recursion. Johnny-Five would not approve of this technique. [Reference to the 1986 movie "Short Circuit."])

Remember, we need to bracket H because it needs to be a list when we do list concatenation with the plus-plus, '++'. T is always a list in [H|T]. H may or may not be a list in [H|T]; either way we need to give it a bracket so it behaves properly.

%%% provides utility functions
                                 %
-module(util).                   
-export([reverse/1]).
                                 %% reverse(L) is for public use
                                 %%   RETURNS: the reverse of a list L
                                 %%   ARG: L <== any list 
reverse( L ) ->                  %% The entry point function: reverse(L) 
    reverse( L, []).             %%   the private function: reverse(L1, L2)
                                 %%
                                 %% reverse() uses the assembly-disassembly method
                                 %%   of recursion.
                                 %% reverse(L1,L2) is the private version of reverse
reverse([], Build) ->            %% The base case: has an empty list for input so
    Build;                       %%   we're done building and return the final answer: Build
reverse([H|T], Build) ->         %% The recursive case: has at least one element: H 
    reverse( T, [H] ++ Build ).  %%   so glue it onto Build and 
                                 %%   recurse with the left-overs: T
% sample output
                                                                      %
c(util).            
  {ok,util}
                                                                      %
util:reverse("abc").
  "cba"
                                                                      %
util:reverse([1,2,3]).
  [3,2,1]
                                                                      %
util:reverse( [ [1,1], [2,2], [3,3]]).
  [ [3,3], [2,2], [1,1]]
                                                                      %
util:reverse([ {1,1}, {2,2}, {3,3} ]).
  [ {3,3}, {2,2}, {1,1} ]
                                                                      %
util:reverse( { [1,1], [2,2], [3,3] } ).
  error

Compile and run.

We can reverse a string, which is really a list. We can reverse a list of integers. We can reverse a list of lists. We can reverse a list of tuples. But we cannot reverse a tuple of lists, because the top level structure is not a list.

If we wanted to be slick, we could use a guard to check the type of the argument at the entry point, then if it is a tuple, change it to a list, execute the reverse, and then change it back to a tuple on the way out.

Please note that the example is purely educational, and in real applications you should avoid appending to the list one element at a time. The correct way to build a list is using the [Head|Tail] form. Often this will cause the list to be in reverse order. To remedy that we then use the lists:reverse built-in-function. See example

%slow
build_append()->build_append(0,[]).
build_append(N,L) when N < 100000 -> build_append(N+1,L++[N]);  % function calls its self with N+1 and a new list with N as the last element.
build_append(_,L) -> L.
%fast
build_insert()->build_insert(0,[]).
build_insert(N,L) when N < 100000 -> build_insert(N+1,[N|L]);   % function calls its self with N+1 and a new list with N as the head.
build_insert(_,L) -> lists:reverse(L).                          % function has to reverse the list before it retuns it.

Compile and run the functions to compare.

The reason behind the speed difference is in the implementation of Erlang lists as linked lists.

Exercises

[edit | edit source]

1) Write a function t_reverse(T) that reverses a Tuple, T, of size 5. (i.e. {a,b,c,d,e})

2) Write a function r_reverse(L) that recursively reverses each sublist in a list of lists, L.

3) Write a function s_reverse(L) that recursively reverses only the sublists but not the top level in a list of lists.

4) Write a function n_reverse(L) that recursively reverses sublists only if they contain integers.


List Comprehensions

List Comprehensions

[edit | edit source]

Intro to Comprehensions

[edit | edit source]

A list comprehension is a mathematical way to construct a list. To do list comprehension we have to use a new operator "<-", "taken from".

L = [ X*X || X <- [1,2,3,4] ].

English translation is: build a list L, with elements that have the value X*X, such that X is taken from the list [1,2,3,4]. It gives the output:

[1,4,9,16]

Notice that this is similar to

lists:map(fun(X) -> X*X end, [1,2,3,4])

In fact, list comprehension provides a shorthand notation for most of the functions in the lists module.

Simple Comprehensions

[edit | edit source]

You can use them to solve equations.

 L = [ {X,Y} || X <- [1,2,3,4], Y <- [1,2,3,4], X*X == Y].

output:

[ {1,1}, {2,4} ]

Lists version

[edit | edit source]

Can you figure out the lists functions used in the above comprehension ?

How about

 F = fun(X, Y) ->
  lists:filter(fun({X0, Y0}) -> X0 * X0 == Y0 end,
     lists:reverse(
        lists:foldl(fun(E, Acc) -> 
             lists:zip(lists:duplicate(length(Y), E), Y) ++ Acc
           end, [], X))) end.
 F([1,2,3,4], [1,2,3,4]).

That is 5 functions in one succinct line. For the remainder of the examples take some time and find the corresponding lists functions that do the same thing.

Permutations

[edit | edit source]

Here we flip two coins:

[ [X]++[Y] || X<-"HT", Y<-"HT"].

Note: strings are lists in erlang. all combinations are: output:

["HH","HT","TH","TT"]

Intermediate list comprehensions

[edit | edit source]

An important use of List Comprehensions is to help translate Prolog-like statements into Erlang. [Why is this "important"?]

1- Erlang is a functional language designed for message passing (MIMD) parallel processing. Prolog is designed for logic programming. Sometimes a problem is most easily defined as a logical set of constraints on some set of data. If one thinks logically or thinks in constraints, or thinks in prolog, this style of list comprehensions can be a helpful way to do your tasks in erlang. There exist many useful solutions in prolog that can be moved to erlang via list comprehensions.

2- Constraint programming and logic programming are considered a higher level way to program than functions, and hence, are a good way to save you time and increase terseness.

Warning: constraint and logic based programs can be harder to debug because strict step by step actions are hidden and delegated to the list comprehension engine. Order of constraints in erlang list comprehensions can affect the output. Order dependence of constraints can be a non-intuitive distraction.

Note: in general, using huge numbers of atoms is not a good idea as they are never garbage collected.

-module(think).              %
-compile(export_all).        %
                             %
male(adam) -> true;          %
male(seth) -> true;
male(cain) -> true;
male(abel) -> true;
male(noah) -> true;
male(_X) -> false.
                             %
female(eve) -> true;
female(_X) -> false.
                             %
parent(adam,cain) -> true;
parent(adam,abel) -> true;
parent(eve,cain) -> true;
parent(eve,abel) -> true;
parent(noah,shem) -> true;
parent(_X,_Y) -> false.
                                                %
people() ->
       [ adam, shem, cain, abel, eve, noah ].
                                                %
father_of() ->
       [ {X,Y} || X <- people(), Y <- people(), parent(X,Y), male(X) ].
mother_of() ->
       [ {X,Y} || X <- people(), Y <- people(), parent(X,Y), female(X) ].

compile with c(think). and generate output with:

17> think:father_of() ++ think:mother_of().
[{adam,cain},{adam,abel},{noah,shem},{eve,cain},{eve,abel}]

Advanced List Comprehensions

[edit | edit source]

Example with quicksort in 7 lines.

-module(sort).
-compile(export_all).
                                % classic way to show off erlang terseness.
qsort([]) ->
   [];
qsort([H | T]) -> 
   qsort([ X || X <- T, X < H ]) ++ [H] ++ qsort([ X || X <- T, X >= H ]).
% sample output:
%
% sort:qsort([1,5,3,7,6,8,9,4]).
%   [1,3,4,5,6,7,8,9]

(Btw: This isn't actually "quicksort," as you'd want to use it, because it uses more memory than necessary, and doesn't get the VM page/cache benefits of an in-place quicksort. But it's neat nevertheless!)

Exercises

[edit | edit source]

1) Write a program using list comprehension that finds the integer solutions {X,Y} for a circle of radius 5.

Solutions

[edit | edit source]

1)

-module( solve_circle).
-export( [start/0] ).
                                                  %
numbers() -> lists:seq(-5,5).
                                                  %
start() -> [ {X,Y} ||
                   X <- numbers(),
                   Y <- numbers(),
                   X*X + Y*Y == 25 ].
                                                   %
% ================================================ %
% sample output
% 11> solve_circle:start().
% [{-5,0}, {-4,-3}, {-4,3}, {-3,-4}, 
%  {-3,4}, {0,-5}, {0,5}, {3,-4},
%  {3,4}, {4,-3}, {4,3}, {5,0}]


List Comments

Comments

[edit | edit source]

The preferred style for comments in Erlang is to use:

 %%% module-level comments
 %%  function-level comments
 %   line-level comments


Variables

Variables in Erlang

Technically there are no variables in Erlang in the sense of multiple assignment. There are Dummy Variables which can take matching values in functions. Once matched their values do not change. Variables in erlang must start with a Capital letter from the Latin 1 character set.

Latin-1 have the following classifications in Erlang:

Decimal       Example  Type of character
--------------------------------------------------
0  - 31                Control character
32                     Space
33 - 47                Punctuation
48 - 57        0-9     Digit
58 - 64                Punctuation
65 - 90        A-Z     Uppercase
91 - 96                Punctuation
97 - 122       a-z     Lowercase
123 - 127              Punctuation
Decimal       Example  Type of character
--------------------------------------------------
128 - 159              Control characters
160 - 191        - ¿   Punctuation 
192 - 214      À - Ö   Uppercase
               ×       Punctuation 
216 - 222      Ø - Þ   Uppercase 
223 - 246      ß - ö   Lowercase 	        
247            ÷       Punctuation 
248 - 255      ø - ÿ   Lowercase

Examples of variables

19> ß = 1.
** exception error: no match of right hand side value 1
20> Þ = a.
a
21> A = ß.
ß

Explanation

Þ is a capital letter so it can be a variable.
ß is not a capital letter so it can not be a variable, but it can be a symbol value.


Kernel and Stdlib

Kernel and Stdlib

[edit | edit source]

The kernel provides simple process-management services, module access, and garbage collection.

The Stdlib includes a large number of commonly used utility functions. Packages included in STDLIB include utilities for math, regexp, printing, various data structures (array, dictionary, tree) and process management (slave, supervisor). A list of functions available in each module can be listed with

 m(Mod).

where Mod is some module from list:

array 
base64 
beam_lib
c
calendar
dets
dict
digraph
digraph_utils
epp
erl_eval
erl_expand_records
erl_id_trans
erl_internal
erl_lint
erl_parse
erl_pp
erl_scan
erl_tar
ets
file_sorter
filelib
filename
gb_sets
gb_trees
gen_event
gen_fsm
gen_server
io
io_lib
lib
lists
log_mf_h
math
ms_transform
orddict
ordsets
pg
pool
proc_lib
proplists
qlc
queue
random
regexp
sets
shell
shell_default
slave
sofs
string
supervisor
supervisor_bridge
sys
timer
win32reg
zip


Distribution

Distributed Processing

[edit | edit source]

Theoretically, in Erlang, writing a parallel program for many computers is not much different from writing a program for a single computer. Each process on each processor has a unique id/name. Processes can be easily spawned. Messages can be easily sent and received. Each home directory on each computer will have a special Erlang cookie password file that allows access to Erlang processes on that machine.

The major problems people have writing distributed programs in Erlang are general network reachability issues related to firewalls and routers. you can make communication between two or more distributed node over network. if you want to communicate with node B from node A. then you can send message to node B as it is local process. you must specify node name to communicate such as:{pid,Nodename}!'hello node A'. where pid is process identifier of process on node B. to more generalization you should register name of the process. for example:register(pro,self()). now you can use pro in place of self().


Behaviors

Behaviors

[edit | edit source]

In Erlang, a behavior is a design pattern implemented in a module/library. It provides functionality in a fashion similar to inheritance in object-oriented programming or interfaces in Java. A number of callback functions must be defined for each behavior to work.

Examples of behaviors are:

  • client-server
  • event-handler
  • gen-server  :gen_server
  • hot-standby
  • keep-me-alive
  • supervision-tree
  • upgrade-handler
  • worker-supervisor

A behavior can be activated with the following code (note: you can use "behavior" instead, if you feel American):

-behaviour(gen_server).


Design Principles

Design Principles

[edit | edit source]

Send Messages, do not share memory

[edit | edit source]

Messages are sent between processes. The compiler is very picky about what operations are "parallel-safe" and is not shy about letting you know. The compiler is particularly picky (parallel-safe) about sending and receiving messages. If you recurse after each message is processed then your code is more parallel-safe. Messages are delivered in order and are selected for removal from the queue by pattern matching (as in Prolog). Because of the extensive built-in pattern matching, Erlang operates in a declarative way. Because of the parallel-picky compiler, it is often as easy to write a parallel program in Erlang as it is to write a serial program in other languages, once you get the hang of the language.

Fail Early and Often

[edit | edit source]

Erlang designers often use an unusual but effective design technique called "fail early and often", FEO. Let processes fail. If unexpected inputs arrive, then let the process crash, and restart it. This allows for terse code that is robust. We do not waste time coding for bad inputs. We code to expected inputs(the specification only). Theoretically, this technique should make for improved security because unexpected inputs cause processes to self destruct and errors and bad behaviour do not propagate.

Debugging

[edit | edit source]

Once a process has crashed we can gather crash information and let the programmer fix problems early. If a process is written to specification, and it crashes then the only possible problem is that it received bad input.


Testing

Testing

[edit | edit source]

The recommended order of testing for an Erlang program is:

  • Test on one core in one microprocessor.
  • Test on multiple cores.
  • Test on multiple computers (if needed).


Documentation

Documentation

[edit | edit source]

Comments

The preferred comment style in erlang is to use:

%%% Three for module level comments.  
%% Double for function level comments. 
% Single for line level comments.


Records

Records in Erlang are syntactic sugar for tagged tuples. The functionality is provided by the preprocessor, not the compiler, so there are some interesting restrictions on how you use them and their support functions.

Defining Records

[edit | edit source]
-record(myrecord, {first_element, second_element}).

The code above defines a record called myrecord with two elements: "first_element" and "second_element". From now on we can use the record syntax #myrecord{}.

Equivalent to Tuples

[edit | edit source]

Records are syntactic sugar for tuples.

#myrecord{first_element=foo, second_element=bar} =:= {myrecord, foo, bar}.
#myrecord{} =:= {myrecord, undefined, undefined}.

The record we defined with two fields is equivalent to a tuple with a tag (the name of the record) and as many elements as the record has fields—two, in our case.


Additional Types

Additional Types

[edit | edit source]

We have already seen the following types: tuple, list, integer, float, function and pid. We can check the type of an object by testing it.

1> is_pid( self() ).
true

If you wish to convert between types, lists are the lingua franca of types in Erlang so make it a list first on your way to something else. Remember, type conversion is not a parallel-safe operation.

Some additional types are: port and reference.

A port is a connection to the external world. Typically ports generate and/or consume bit streams. Binary data is considered untyped data in Erlang. (See BitSyntax).

A reference is a globally unique symbol and is generated with:

19> make_ref(). 
#Ref<0.0.0.88>

A reference is only useful when a unique tag is needed across all connected processes. Do not confuse the term reference with a references in C, which points to data. An Erlang reference is only a unique tag.

Erlang Has No Boolean Type

[edit | edit source]

Erlang has no Boolean type. It has the atoms (true and false) which are generated by certain functions and expected by certain functions like guards and list comprehensions. The function: is_constant() generates either true or false.

We can test whether an object is a constant.

1> is_constant(a) 
true
2> is_atom(a).
true

Because atoms are represented as constant numbers, atoms are constants.

is_constant(A). 
** 1: variable 'A' is unbound **
5> A=1.
1
6> is_constant(A).
true

Theoretically, because the Boolean type is not built in, it should make it easier to let Erlang compute with alternate types of logic, {true, false, null} for instance.


Function Objects

Function Objects

[edit | edit source]

Function objects can be named or unnamed, or stored in a variable. Function objects can be like lambda expressions. Lambda expression in Erlang are created with the keyword "fun". Lambda expressions are unnamed functions that can be stored in variables. Please consider the following:

Sample Erlang command line code:

Mod = fun(X,Y) -> X rem Y end.
Mod(6,5).
 1

Generic behaviors often use function objects to become specialized. A generic server can be dynamically given a function object to allow it to become a particular type of server or service provider.


Bitsyntax

Bit Strings

[edit | edit source]

Erlang lets us use Bit Strings. They have the form

<<Value:Bitlength>> or <<v1:length1,v2:length2,...>>

The default bit length is 8.

65> <<1:8>> == <<1>>. 
true

Integers used in bit strings can be padded on the left with zeroes.

66> <<1:8>> == <<00000001>>. 
true

Some bit strings have string-like representations.

38> <<00011111>>.
<<"g">>

Remember that using the default bit length will cause truncation of too-large integers!

39> <<"g">> == <<103>>.
true
40> <<00011111>> == <<103>>.
true
41> <<00011111:8>> == <<103>>.
true
42> <<00011111:16>> == <<43, 103>>.
true
43> <<00011111:24>> == <<0, 43, 103>>.
true
44> <<00011111:32>> == <<0, 0, 43, 103>>.
true

We cannot specify individual bits with integers. We must use values with a bit length of one.

67> <<0101>>.
<<"e">>
68> <<1:1, 0:1, 1:1>>.
<<5:3>>
69> <<0101>> == <<1:1, 0:1, 1:1>>.
false
70> <<101>> == <<1:1, 0:1, 1:1>>.
false
71> <<1:1, 0:1, 1:1>> == <<5:3>>.
true
72> <<0:5, 1:1, 0:1, 1:1>>.
<<5>>
73> <<0:5, 1:1, 0:1, 1:1>> == <<0101>>.
false

We can select parts of a bit string with pattern matching.

45> <<H:2,T:6>> = <<"A">>. 
<<"A">>
46> H. 
1
47> T.
1 
86> <<01000001:8>> == <<"A">>.
true
87> <<1:2,1:6>> == <<"A">>.
true
88> <<65>> == <<"A">>.
true

We can match X to a value.

95> <<1:2,X:6>> = <<"A">>. 
<<"A">>
96> X.
1

We can not match Y to a bit length.

97> <<1:2,1:Y>> = <<"A">>.
** 1: variable 'Y' is unbound **

We can use a bound variable as a bit length.

98> Z = 6.
6
99> <<1:2,X:Z>> = <<"A">>.
<<"A">>
100> X.
1


Example1

Objects with Erlang

Erlang is a functional programming language and a concurrency oriented programming languages (Armstrong, dissertation, 2005), but Erlang does not have explicit built-in object oriented language features. An object oriented programming style can be achieved by alternative means easily. It is particularly easy to do object oriented programming if we restrict ourselves to single inheritance. One can use processes to represent classes and messages to represent methods. To do so, each object when created can create a chain of processes that represent their ancestors in the inheritance chain. The methods(messages) can be passed up the chain until they reach a process that has a matching method. If the message reaches the top of the chain(the Object class or above that dev_nul) then we can generate an exception for a "bad method name". Each class(process) will maintain its own attributes(variables) in its own recursive argument call list. These attributes can be accessed and updated with messages such as get and set. Attributes are stored in a dictionary called a Bundle, in each class process.

Included is some example code that creates OOP using the described technique. In the program, we create an instance of the class integer. Its parent float, knows how to take the square root of reals. Its parent complex, knows how to take the square root of negative numbers. Its parent matrix, knows how to take the square root of a diagonal matrix.

Logically, the traditional class relationship is held in the class diagram. An integer is a Real. A Real(float) is a (subset of) complex. An a complex number is a (subset of) complex matrices, if we think of a (1 by 1) matrix as a single number.

A message(method) is sent to an instance of an object. If a process does not know how to do something, it passes a message(method) to its parent(process in this situation). If we tried to do something like take the sqrt(non-diag-matrix) it would be passed up to dev_nul and generate an error.

The start function creates an instance of the integer class. Then it asks the instance to calculate the square_root of 4 numbers: 4, 0.04, -4 and the matrix [[4,0],[0,9]]. The answers are: 2, 0.2, {2, i} and [[2,0],[0,3]].

----------------------------------------------------------------
 Original        Each object has its own process for each of its ancestors 
 Classes         (Process chain for object Obj_1)

+---------+     +---------+
| dev_nul |     | dev_nul |
+---------+     +---------+
  / \             / \    
   |               |    
   |               |      
+-----------+   +-----------+ 
| Object    |   | Object    |
+-----------+   +-----------+
| id        |   | id        |
| classname |   | classname |
| name      |   | name      |
+-----------+   +-----------+
| get()     |   | get()     |
+-----------+   +-----------+ 
  / \             / \     
   |               |     
   |               |      
+---------+     +---------+
| Matrix  |     | Matrix  |
+---------+     +---------+
+---------+     +---------+
| sqrt()  |     | sqrt()  |
+---------+     +---------+
  / \             / \     
   |               |      
   |               |      
+---------+     +---------+
| Complex |     | Complex |
+---------+     +---------+
+---------+     +---------+
| sqrt()  |     | sqrt()  |
+---------+     +---------+ 
  / \             / \     
   |               |      
   |               |     
+---------+     +---------+
| Float   |     | Float   |
+---------+     +---------+
+---------+     +---------+
| sqrt()  |     | sqrt()  |
+---------+     +---------+ 
  / \             / \     
   |               |      
   |               | 
+---------+     +---------+
| Integer |     | Integer |
+---------+     +---------+
+---------+     +---------+
| sqrt()  |     | sqrt()  |
+---------+     +---------+
---------------------------------------
Program output:
1> mathobjects:start().
[
[{id,#Ref<0.0.0.27>},{class_name,integer},{name,book}],
2.00000, 0.20000,
{2.00000, i},
[[2.00000,0],[0,3.00000]]
]
--------------------------------------
-module(mathobjects).
-compile(export_all).

start()->
       Obj_1         = spawn_link(mathobjects, integer, []),  
	Id_1 	      = rpc(Obj_1, {get, id}),             
	Name_1        = rpc(Obj_1, {get, name}),           
	Class_Name_1  = rpc(Obj_1, {get, class_name}),     
	% -------------------------------
	R0 = [ Id_1, Class_Name_1, Name_1 ],
	R1 = rpc(Obj_1, {sqrt, 4}),
	R2 = rpc(Obj_1, {sqrt, 0.04}),
	R3 = rpc(Obj_1, {sqrt, -4}),
	R4 = rpc(Obj_1, {sqrt, [[4,0],[0,9]]}),
	[R0, R1, R2, R3, R4].
	
rpc(Dest, Msg) ->
	Dest ! {self(), Msg},
	receive
		Answer -> 
			Answer
	after 1000 ->
		ok
	end.

dev_null() ->
	receive
		{From, Any} -> From ! {Any, attribute_unknown}
	end,
	dev_null().

object() ->
	Id 		= erlang:make_ref(),
	Class_Name 	= object,
	Bundle 		= dict:from_list([ {id,Id}, {class_name, Class_Name} ]),
	Parent 		= spawn(objects, dev_null, []),
	object(Parent, Bundle).

object(Parent, Bundle) ->
	receive
		{From, {get, Attribute}} ->
			handle_get_attribute(Attribute, From, Bundle, Parent)
	end,
	object(Parent, Bundle).

% default constructor 

matrix() ->
	Class_Name      = matrix,
	Name            = book,
	Parent 	        = spawn_link(mathobjects, object, []),
	Parent_Class    = object,
	Bundle = dict:from_list( [
		{class_name, Class_Name},
		{parent_class, Parent_Class},
		{name, Name},
		{parent, Parent}]),
	matrix(Parent, Bundle).
	
matrix(Parent, Bundle) ->
	receive
		{From, {get, Attribute}} ->
			handle_get_attribute(Attribute, From, Bundle, Parent);
		{set, Attribute, Value} ->
			NBundle = handle_set_attribute(Attribute, Value, Bundle, Parent),
			matrix(Parent, NBundle);
		{From, {sqrt, [[A,B],[C,D]]}} when B==0, C==0 ->
			Out = [[math:sqrt(A),0],[0,math:sqrt(D)]],
			From ! Out; 
		Any ->
			Parent ! Any
	end,
	matrix(Parent, Bundle).
	
complex() ->
	Class_Name      = complex,
	Name            = book,
	Parent 	        = spawn_link(mathobjects, matrix, []),
	Parent_Class    = object,
	Bundle = dict:from_list( [
		{class_name, Class_Name},
		{parent_class, Parent_Class},
		{name, Name},
		{parent, Parent} ] ),
	complex(Parent, Bundle).
	
complex(Parent, Bundle) ->
  	receive
		{From, {get, Attribute}} ->
			handle_get_attribute(Attribute, From, Bundle, Parent);
		{set, Attribute, Value} ->
			NBundle = handle_set_attribute(Attribute, Value, Bundle, Parent),
			complex(Parent, NBundle);
		{From, {sqrt, Arg}} when is_list(Arg) ->
			Parent ! {From, {sqrt, Arg}};
		{From, {sqrt, Arg}} when Arg < 0 ->
			Out = {math:sqrt(0-Arg), i},
			From ! Out;
		Any ->
			Parent ! Any
	end,
	complex(Parent, Bundle).
	
float() ->
 	Class_Name      = float,
	Name            = book,
	Parent 	        = spawn_link(mathobjects, complex, []),
	Parent_Class    = object,
	Bundle = dict:from_list( [
		{class_name, Class_Name},
		{parent_class, Parent_Class},
		{name, Name},
		{parent, Parent}]),
	float(Parent, Bundle).
	
float(Parent, Bundle) ->
	receive
		{From, {get, Attribute}} ->
			handle_get_attribute(Attribute, From, Bundle, Parent);
		{set, Attribute, Value} ->
			NBundle = handle_set_attribute(Attribute, Value, Bundle, Parent),
			float(Parent, NBundle);
		{From, {sqrt, Arg}} when is_list(Arg) ->
			Out = rpc(Parent, {sqrt, Arg}),
			From ! Out;
		{From, {sqrt, Arg}} when Arg < 0 ->
			Out = rpc(Parent, {sqrt, Arg}),
			From ! Out;  
		{From, {sqrt, Arg}} ->
			Out = math:sqrt(Arg),
			From ! Out; 
		Any ->
			Parent ! Any
	end,
	float(Parent, Bundle).
	
integer() ->
	Class_Name      = integer,
	Name            = book,
	Parent 	        = spawn_link(mathobjects, float, []),
	Parent_Class    = object,
	Bundle = dict:from_list( [
		{class_name, Class_Name},
		{parent_class, Parent_Class},
		{name, Name},
		{parent, Parent}]),
	integer(Parent, Bundle).
	
integer(Parent, Bundle) ->
	receive
		{From, {get, Attribute}} ->
			handle_get_attribute(Attribute, From, Bundle, Parent);
		{set, Attribute, Value} ->
			NBundle = handle_set_attribute(Attribute, Value, Bundle, Parent),
			integer(Parent, NBundle);
		{From, {sqrt, Arg}} when is_float(Arg) ->
			Out = rpc(Parent, {sqrt, Arg}),
			From ! Out;
		{From, {sqrt, Arg}} when is_list(Arg) ->
			Out = rpc(Parent, {sqrt, Arg}),
			From ! Out;
		{From, {sqrt, Arg}} when Arg < 0 ->
			Out = rpc(Parent, {sqrt, Arg}),
			From ! Out;
		{From, {sqrt, Arg}} ->
			Out = 
				try math:sqrt(Arg)  
					catch
						_AnyException ->
							rpc(Parent, {From, sqrt, Arg})
					end,
			From ! Out;
		Any ->
			Parent ! Any
	end,
	integer(Parent, Bundle).
	
% -----------------------------------------------
 	
handle_set_attribute(Attribute, Value, Bundle, Parent) ->	
			Found = dict:find(Attribute, Bundle),
			if 
				is_tuple(Found) ->     % if attribute exists then set it
					{ok, _} = Found,
					NBundle = dict:store(Attribute, Value, Bundle),
					NBundle;
				true ->
					Parent ! {set, Attribute, Value}
			end.
			
handle_get_attribute(Attribute, From, Bundle, Parent) ->	
			Found = dict:find(Attribute, Bundle),
			if 
				is_tuple(Found) ->
					{ok, Value} = Found,
					From ! {Attribute, Value};
				true ->
					Parent ! {From, {get, Attribute}}
			end.


Using lists

Using lists

[edit | edit source]

foreach

[edit | edit source]
3> lists:foreach( fun(X)->X*X end, [1,2,3]).
>

produces no output because the purpose of foreach is to generate side-effects. However,

4> lists:foreach( fun(X)->io:format("~w ",[X]) end, [1,2,3,4]).
1 2 3 4

does work, because io:format() is a side effect function.

sequence of numbers

[edit | edit source]

lists:seq(1,100) is like range(1,101) in python.

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

lists:sort( A ) is what you think.

6> lists:sort([1,3,2,6,5,4]).
[1,2,3,4,5,6]
7> lists:sort([a,d,b,c]).
[a,b,c,d]
8> lists:sort([f,e,a,"d","c",{b}]).
[a,e,f,{b},"c","d"]


Using regexp

Regular Expressions

[edit | edit source]
2> re:run("hello world","w.+d").
{match,[{6,5}]}

The regular expression, "w.+d" matches the string, "hello world" at location 6 for 5 chars.

9> re:replace("10203040","([2-4]0)+","01",[{return,list}]).
"1001"

Makes a substitution, replacing "203040" with "01".


Debugging and Tracing

Tools

[edit | edit source]
  • Debugging
    • dbg
    • debugger
  • Tracing
    • ttb
    • invision
    • et
  • Coverage
    • Cover

Coverage

[edit | edit source]

Coverage shows what functions are covered by a test. Sample program: test_rotate.erl

-module(test_rotate).
-export([test/0]).

test() ->
  assert( left_rotate([a,b,c]), [b,c,a] ).

assert(X, X) -> true.

left_rotate([]) -> [];
left_rotate([H|T]) -> T ++ [H].

Sample output:

33> cover:compile(test_rotate).
34> test_rotate:test().
true
35> cover:analyse_to_file(test_rotate, "cover.html", [html]).
   

Contents of cover.html shows that each clause was run once except for left_rotate([]). Each time the program is tested, the run count for each visited clause is increased by one. Cover sample output file:

File generated from test_rotate.erl by COVER 2008-04-23   
at 12:49:11
*********************************************
        |  -module(test_rotate).
        |  -export([test/0]).
        |   
        |  test() ->
     1..|    assert( left_rotate([a,b,c]), [b,c,a] ).
        |  
     1..|  assert(X, X) -> true.
        |  
     0..|  left_rotate([]) -> []; 
     1..|  left_rotate([H|T]) -> T ++ [H].
        |


Performance and Optimization

Profiling

[edit | edit source]

There are a number of profiling libraries available:

  • cprof
  • eprof
  • fprof

Profiling of any kind has a runtime performance penalty - the reason for the different libraries above is their different trade-offs in terms of profiling precision and the performance penalty for using them.


Unit Testing with eunit

Installing eunit

[edit | edit source]

This section will show you how to install eunit on different operating systems. Once you have installed eunit its use is fairly consistent across the different operating systems.

ubuntu

[edit | edit source]

Get source with:

    svn co http://svn.process-one.net/contribs/trunk/eunit eunit

Compile code with:

    .../eunit$ make

Copy to /usr/lib/erlang/lib

Test the Installation

[edit | edit source]

To test that everything is installed ok before writing any tests create a file test01.erl with the following contents:

  -module(test01).
  -compile(export_all).
  -include_lib("eunit/include/eunit.hrl").

Compile this file as follows:

  $ erl
  > c(test01).
  {ok,test01}

If you get the result {ok,test01} then you've installed eunit correctly.

Using eunit

[edit | edit source]

We'll start by writing a passing test and a failing test. In the file you created to check the installation add the following:

passing_test() -> ?assert(true).

failing_test() -> ?assert(false).

Run the tests as follows.

Eshell V5.5.5  (abort with ^G)
1> c(test01).
{ok,test01}
2> test01:test().
test01:failing_test...*failed*
::error:{assertion_failed,[{module,test01},
                         {line,29},
                         {expression,"false"},
                         {expected,true},
                         {value,false}]}
  in function test01:'-failing_test/0-fun-0-'/0

=======================================================
  Failed: 1.  Aborted: 0.  Skipped: 0.  Succeeded: 3.
error
3>


Database Programming

Databases in Erlang

[edit | edit source]

Dictionary

[edit | edit source]

Data can be stored in many ways in Erlang. Each process has a local dictionary that uses put and get.

2> put(hello,world).  
undefined
3> get(hello).
world

Data can be stored with the ETS (Erlang table storage system) via the ETS library. It creates data at the process level. See Using_Ets.

Mnesia

[edit | edit source]

Mnesia is a distributed database that lives in the local file system.

External Databases

[edit | edit source]


Using ets

ETS local data storage

[edit | edit source]

ETS is the Erlang table storage system, which provides hash-based data storage and access functions. These functions run in constant time. ETS data is stored in a process as long as it is running.

Here is an example of how to use some simple functions in ETS. Notice that the table is not square because goofy has no last name.

Sample program: test_ets.erl

[edit | edit source]
-module(test_ets).
-compile(export_all).

start() -> start( mouse ).

start( Animal ) ->
	Kingdom = ets:new( 'magic',  [] ),
	% note: table is not square
	populate( Kingdom, [{micky,mouse}, {mini,mouse}, {goofy}] ),
	Member = ets:member( Kingdom, micky ),
	io:format( " member ~w ~n ", [ Member ] ),
	%% show_next_key( Kingdom, micky ),
        %% Not work as expected in OTP 19.2
        %% Do a minor change
        FirstKey = ets:first(Kingdom),
        show_next_key(Kingdom, FirstKey),
	find_animal( Kingdom, Animal ).
	
show_next_key( _Kingdom, '$end_of_table' ) -> done;
show_next_key( Kingdom,  Key) ->
	Next = ets:next( Kingdom, Key ),
	io:format( " next ~w ~n ", [ Next ] ),
	show_next_key( Kingdom, Next ).

populate( _Kingdom, [] ) -> {done,start};
populate( Kingdom, [H | T] ) ->
		ets:insert( Kingdom, H ),
		populate( Kingdom, T ).
	
find_animal( Kingdom, Animal ) ->
	ets:match( Kingdom, { '$1', Animal } ).
% ==============
% sample output
% ==============
% 53> test_ets:start().
% member true 
%  next mini 
%  next goofy 
%  next '$end_of_table' 
%  [[mini],[micky]]


Using mnesia

Using Mnesia

[edit | edit source]

Mnesia is the distributed database written in Erlang, meant to mainly be used by Erlang programs. Simple queries can be written with query list comprehensions (qlc). Realtime queries can decide to not use transactions.

Explain program: art.erl

[edit | edit source]

The program defines some functions that work similar to SQL. We wish to build a table of artwork named "painting", with fields: index, artist, and title.

Table: painting
 
+---------------------------------------------+ 
| index | artist  | title                     |
+---------------------------------------------+  
| 1     | Dali    | The Ghost of Vermeer      |
| 2     | Dali    | The Persistance of Memory |
| 3     | Vermeer | Girl With Pearl Earring   |
+---------------------------------------------+

art:init(). Starts the database and creates the table: painting.
insert() Puts data into the table
select() Gets data out of the table

Note that the second time we call init() the database is restarted but we get an error when it tries to recreate the table (as it already exists).

Example program: art.erl

[edit | edit source]
-module(art).
-compile(export_all).
-include_lib("stdlib/include/qlc.hrl"). 
 
-record(painting, {index, artist, title}).
  
init() ->
    mnesia:create_schema([node()]),
    mnesia:start(),
    mnesia:create_table(painting,
        [ {disc_copies, [node()] },
             {attributes,      
                record_info(fields,painting)} ]).
 
insert( Index, Artist, Title) ->
    Fun = fun() ->
         mnesia:write(
         #painting{ index=Index,
                   artist=Artist, 
                        title=Title    } )
               end,
         mnesia:transaction(Fun).
  
select( Index) ->
    Fun = 
        fun() ->
            mnesia:read({painting, Index})
        end,
    {atomic, [Row]}=mnesia:transaction(Fun),
    io:format(" ~p ~p ~n ", [Row#painting.artist, Row#painting.title] ).

select_some( Artist) ->
    Fun = 
        fun() ->
            mnesia:match_object({painting, '_', Artist, '_' } )
        end,
    {atomic, Results} = mnesia:transaction( Fun),
    Results.
 
select_all() -> 
    mnesia:transaction( 
    fun() ->
        qlc:eval( qlc:q(
            [ X || X <- mnesia:table(painting) ] 
        )) 
    end ).
  
select_search( Word ) -> 
    mnesia:transaction( 
    fun() ->
         qlc:eval( qlc:q(
              [ {F0,F1,F2,F3} || 
                   {F0,F1,F2,F3} <- 
                        mnesia:table(painting),
                        (string:str(F2, Word)>0) or  
                        (string:str(F3, Word)>0)
               ] )) 
    end ).

Sample output: art.erl

[edit | edit source]
%   Sample output:
 
%   6> c(art).
%   {ok,art}
  
%   7> art:init(). 
%   {atomic,ok}
 
%   6> art:insert(1,"Dali","The Ghost of Vermeer").
%   {atomic,ok}
  
%   7> art:select(1).
%   "Dali" "The Ghost of Vermeer"
%   ok
  
%   8> art:insert(2,"Dali","The Persistence of Memory").
%   {atomic,ok}
   
%   9> art:select(2).
%   "Dali" "The Persistence of Memory"
%   ok
   
%   10> art:select(1).
%   "Dali" "The Ghost of Vermeer"
%   ok 
 
%   25> art:insert(3,"Vermeer", "Girl With Pearl Earring").
%   {atomic,ok}
 
%   26> art:select_some("Dali").
%   [{painting,1,"Dali","The Ghost of Vermeer"},
%   {painting,2,"Dali","The Persistence of Memory"}]
 
%   27> art:select_all().
%   {atomic,[{painting,1,"Dali","The Ghost of Vermeer"},
%               {painting,2,"Dali","The Persistence of Memory"},
%               {painting,3,"Vermeer","Girl With Pearl Earring"}]}

%---to run a new session after restarting erlang---
 
%   2> art:init().
%   {aborted,{already_exists,painting}}

%   3> art:select_search("Vermeer").
%   {atomic,[{painting,1,"Dali","The Ghost of Vermeer"},
%         {painting,3,"Vermeer","Girl With Pearl Earring"}]}


Autonomous Agents

Autonomous Agents

[edit | edit source]

Dynamic timeout based initiative switching

[edit | edit source]

Explanation of the code in jungle.erl

[edit | edit source]

Here we have a simple chat-bot agent called person/4. We create two instances of it called Tarzan and Jane. They talk to each other. Each has a timeout. The timeout is the length of time one will wait before they initiate conversation. The initial timeout of Jane is set to 10 seconds. The initial timeout of Tarzan is set to 8 seconds. Because of the initial values, Tarzan will speak first and Jane will respond. Both timeouts start over but keep the same values. Again, Tarzan speaks first and Jane responds. Now things get interesting. The agent can tell if the conversation is repeating. If the conversation repeats then special messages are sent to cause a swap in the relative levels of timeout. Now Tarzan waits longer than Jane and Jane has a chance to speak first. Now, Jane speaks first twice. Then they swap initiative again. Since the processes are autonomous, we need to stop them with a quit program called jungle:quit(). Note: the change in timeout length is either double or half. The timeout change is similar to the exponential binary backoff for ethernet collisions. External link: [4] Exponential_backoff.

Example program listing: jungle.erl

[edit | edit source]
-module( jungle ).
-compile(export_all).
   
%% This program shows how chat-bot agents can exchange initiative(lead) while in conversation.
%%   Start with start().
%%   End with quit().
 
start() ->
    register( tarzan, spawn( jungle, person, [ tarzan,  8000, "", jane ]   ) ),
    register( jane, spawn( jungle, person,   [ jane,   10000, "", tarzan ] ) ),
    "Dialog will start in 5ish seconds, stop program with jungle:quit().".
                                                                                         
quit() ->
    jane ! exit,
    tarzan ! exit.
 
%% Args for person/4
%%   Name:  name of agent being created/called
%%   T:     timeout to continue conversation
%%   Last:  Last thing said
%%   Other: name of other agent in conversation
  
person( Name, T, Last, Other ) ->
    receive
        "hi" -> 
            respond( Name, Other, "hi there \n " ),
            person( Name, T, "", Other );
        "slower" -> 
            show( Name, "i was told to wait more " ++ integer_to_list(round(T*2/1000))),
            person( Name, T*2, "", Other );
        "faster" -> 
            NT = round( T/2 ),
            show( Name, "I was told to wait less " ++ integer_to_list(round(NT/1000))),
            person( Name, NT, "", Other );
        exit ->
            exit(normal);
        _AnyWord -> 
            otherwise_empty_the_queue,
            person( Name, T, Last, Other )
    after T -> 
       respond( Name, Other, "hi"),
       case Last of
           "hi" ->
           self() ! "slower",
           sleep( 2000),                  % give the other time to print
           Other ! "faster",
           person( Name, T, "", Other );
       _AnyWord ->
           person( Name, T, "hi", Other )
       end
   end.
                                                                                             %
respond( Name, Other, String ) ->
    show( Name, String ),
    Other ! String.
                                                                                             %
show( Name, String ) ->
    sleep(1000),
    io:format( " ~s -- ~s \n ", [ Name, String ] ).
                                                                                             %
sleep(T) ->
    receive 
    after T ->
        done
    end.
% ===========================================================>%

Sample output from: jungle.erl

[edit | edit source]
Sample output:
 	
 18> c(jungle).
 {ok,jungle}
  
 19> jungle:start().
 jane_and_tarzan_will_start_in_5_seconds
  
 tarzan—hi 
 jane—hi there 
 
 tarzan—hi 
 jane—hi there 
 
 jane—I was told to wait less: 5 
 tarzan—I was told to wait more: 16 
  
 jane—hi 
 tarzan—hi there 
 
 jane—hi 
 tarzan—hi there 
 
 tarzan—I was told to wait less: 8 
 jane—I was told to wait more: 10 
 
 tarzan—hi 
 jane—hi there 
 
 tarzan—hi 
 jane—hi there 
 
 jane—I was told to wait less: 5 
 tarzan—I was told to wait more: 16 
 
 jane—hi 
 tarzan—hi there 
 
20> jungle:quit(). 
exit


Parallel Programming

Prime Sieve (parallel with linda type coordination)

[edit | edit source]

Linda is a way to coordinate the actions of multiple processors. A tuplespace is created for the candidate primes to live. The sieve processes are created and send messages to the tuplespace to remove non-primes. The sieve processes also send messages to each others to remove the non-prime sieves.

How many processors can this program use? This program creates as many sieves as the square root of the numbers in the matrix (tuplespace). If we are looking for the primes below 100 then there are about 10 parallel sieve processes. Actually, most of the sieve processes are halted and only (the number of prime numbers under the square root of Max) processes are left at the end. This allows an easy parallelism of 10 for 100 and 100 for 10000 with little modification.

In Erlang, it is not recommended that one use a huge number of atoms, so N should not get too large. We also might run out of processes or memory.

This program breaks one of the general rules of Erlang process management: do not use peer managers. Each of the sieve processes is a peer manager because each sieve may halt any other sieve. Rather, processes should be managed in a top-down tree structure. The peer management of the sieves causes some nasty timing issues. Timing is one reason why peers are usually a bad idea.

When the sieve for 2 ends, the list of primes is dumped. Erlang should give each process equal CPU time. When the slices are even, the sieve for 2 starts first and ends last. When some other sieve is starved for time, it may end after sieve 2 and the prime number dump will be too early, leaving some numbers divisible by 2 in the list of putative primes. Relative starvation of some process happens about 1 out of 10 times. Clearly the critical word should be: "tries". "Erlang TRIES to give each process equal time slices."

Does it hurt to have non-prime sieves running? We can use a 6-sieve. A 6-sieve is redundant because the 2-sieve and the 3-sieve remove all numbers that the 6-sieve would remove. By removing the 6-sieve and its non-prime sieve brothers we reduce the number of messages that the matrix must handle thereby speeding the final result.

Prime Sieve Program (parallel)

[edit | edit source]
   -module(primes).

   % This is a simple linda tuplespace. Here we use it to find primes numbers.
   % This tuplespace cannot have duplicate tuples, but with a prime sieve it does
   % not matter.

   -compile(export_all).

   start() -> start(100).  % default value for max is 100
   start(Max) -> 
       io:format("  Loading ~w numbers into matrix (+N) \n ", [ Max ] ),
       Lid = spawn_link( primes, linda, [Max, [], [] ]),
       Sqrt = round(math:sqrt(Max)+0.5),  
       io:format(" Sqrt(~w) + 1 = ~w \n " , [Max,Sqrt] ),  
       io:format(" Tuple space is started ~n ",[]),  
       io:format(" ~w sieves are spawning (+PN) ~n ", [Sqrt] ),
       io:format(" Non prime sieves are being halted (-PN) ~n ", [] ),
       % load numbers into tuplespace 
       % and spawn sieve process
       spawn( primes, put_it, [Max, Max, Lid] ).

   start_sieves(Lid) ->
       Lid ! {self(), get, all, pids},  
       receive 
           {lindagram, pids, Pids} -> done
       end,
       start_sieve_loop(Pids).

   start_sieve_loop([]) -> done;
   start_sieve_loop([Pid|Pids]) ->
       receive
       after 100 -> done
       end,
       Pid ! {start},
       start_sieve_loop(Pids).

   spawn_sieves( _Max, Sqrt, _Lid, Sqrt ) -> done;  
   spawn_sieves( Max, Inc, Lid, Sqrt ) ->
       T = 1000,
       Pid = spawn( primes, sieve, [ Max, Inc+Inc, Inc, Lid, T ]),
       Name = list_to_atom("P" ++ integer_to_list(Inc)),
       Lid ! {put, pid, Name},
       register( Name, Pid),
       io:format(" +~s ", [atom_to_list(Name)]),
       spawn_sieves( Max, Inc+1, Lid, Sqrt ).

   put_it(Max, N, Lid) when N =< 1 ->
       Sqrt = round(math:sqrt(Max)+0.5),
       spawn_sieves( Max, 2, Lid, Sqrt );  

   put_it(Max, N,Lid) when N > 1 ->
       receive
       after 0 ->
           Lid ! {put, N, N},
           if 
               N rem 1000 == 0 ->
                   io:format(" +~w ", [N]);
               true -> done
           end,
           put_it(Max, N-1,Lid)
       end.

   % the 2 sieve starts last and has the most to do so it finishes last
   sieve(Max, N, 2, Lid, _T) when N > Max -> 
       io:format("final sieve ~w done, ~n", [2] ),
       Lid ! {dump,output};

   sieve(Max, N, Inc, _Lid, _T) when N > Max ->    
       io:format("sieve ~w done ", [Inc] );

   sieve(Max, N, Inc, Lid, T) when N =< Max ->   
       receive 
       after 
           T -> NT = 0   
       end,
       receive 
           {lindagram,Number} when Number =/= undefined -> 
               clearing_the_queue;
           {exit} -> exit(normal)
       after
           1 -> done 
       end,

       % remove multiple of number from tuple-space
       Lid ! {self(), get, N},
       Some_time = (N rem 1000)==0,
       if Some_time -> io:format("."); true -> done end,

       % remove (multiple of Inc) from sieve-process space
       Name = list_to_atom("P" ++ integer_to_list(N)),
       Exists = lists:member( Name, registered()),
       if 
           Exists ->
               Name ! {exit},
               io:format(" -~s ", [atom_to_list(Name)] );
           true -> done
       end,
       sieve(Max, N+Inc, Inc, Lid, NT).        % next multiple
        
   %% linda is a simple tutple space 
   %%    if it receives no messages for 2 whole seconds it dumps its contents 
   %%    as output and halts

   linda(Max, Keys, Pids) ->
       receive
       {put, pid, Pid} ->
           linda(Max, Keys, Pids++[Pid]);
       {put, Name, Value} ->
           put( Name, Value),
           linda(Max, Keys++[Name], Pids);
       {From, get, Name} ->
           From ! {lindagram, get( Name)},
           erase( Name ),                          % get is a destructive read  
           linda(Max, Keys--[Name],Pids);
       {From, get, all, pids} ->
           From ! {lindagram, pids, Pids},
           linda(Max, Keys, Pids );
       {From, get, pid, Pid} ->
           L1 = length( Pids ),
           L2 = length( Pids -- [Pid]),
           if 
               L1 > L2 ->  % if it exists
                   From ! {lindagram, pid, Pid};
               true -> 
                   From ! {lindagram, pid, undefined}
           end,
           linda(Max, Keys, Pids );
       {dump,output} ->
           io:format(" ~w final primes remain: ~w ~n ", [length(Keys),  lists:sort(Keys) ])
       after (100*Max) -> % if there is not tuple action after some time then print the results
           io:format(" ~w primes remain: ~w ~n ", [length(Keys),  lists:sort(Keys) ])
       end.

Sample Output for Prime Sieve

[edit | edit source]
c(primes).
primes:start(1000).
 Loading 1000 numbers into matrix (+N)
 Sqrt(1000) + 1 = 32
 Tuple space is started
 32 sieves are spawning (+PN)
 Non prime sieves are being halted (-PN)
 +1000 <0.46.0>
+P2  +P3  +P4  +P5  +P6  +P7  +P8  +P9  +P10  
+P11  +P12  +P13  +P14  +P15  +P16   
+P17  +P18  +P19  +P20  +P21  +P22  +P23  +P24  
+P25  +P26  +P27  +P28  +P29  +P30  
+P31  -P8  -P6  -P4  -P9  -P12  -P10  -P15  
-P15  -P18  -P14  -P21  -P21  -P22  
-P26  -P20  -P24  -P25  -P27  -P28  -P30  -P30  -P16 
sieve 31 done sieve 29 done 
sieve 19 done sieve 23 done sieve 11 done 
sieve 13 done sieve 17 done sieve 7 done 
.sieve 5 done sieve 3 done .final sieve 2 done,
168 final primes remain: 
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,
71,73,79,83,89,97,
101,103,107,109,113,127,131,137,139,149,151,157,163,
167,173,179,181,191,193,197,199,
211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,
307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,
401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,
499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,
601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,
701,709,719,727,733,739,743,751,757,761,769,773,787,797, 
809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,
907,911,919,929,937,941,947,953,967,971,977,983,991,997]


Making Parsers with yecc

Making Parsers with yecc

Yecc is an erlang version of yacc/bison.

We have a BNF(Backus-Naur_form) grammar in a source file ending in .yrl, yrl means yecc rule list. We can parse a simple xhtml file using yecc. Actually, we will apply yecc to html.yrl to create a parser called html_parser.erl. Next we use the html_parser to parse some xhtml, voila.

yecc:yecc("html.yrl","html_parser.erl").
c(html_parser).
f(B), {_,B,_} =  
erl_scan:string(
"<html><head></head><body>hello_world</body></html>").
html_parser:parse(B).

All tags in the xhtml code must have matching open and close. (Of course a more powerful way to parse an xml file in erlang is to use xmerl).

html.yrl source:

Nonterminals tag elements element start_tag end_tag .
Terminals 'atom' '<' '>' '/'.
Rootsymbol tag.
tag -> 
	start_tag tag end_tag : 
	['$1', '$2', '$3'].
tag -> 
	start_tag tag tag end_tag : 
	['$1', '$2', '$3', '$4'].
tag -> 
	start_tag elements end_tag : 
	['$1', {'contents','$2'}, '$3'].   
tag -> 
	start_tag end_tag : 
	['$1','$2'].

start_tag -> '<' 'atom' '>' : {'open','$2'}.   
end_tag -> '<' '/' 'atom' '>' : {'close','$3'}.   
elements -> element : ['$1'].
elements -> element elements : ['$1', '$2'].
element -> atom : '$1'.

% yecc:yecc("html.yrl","html_parser.erl").
% c(html_parser).
% f(B), {_,B,_} =  
% erl_scan:string(
% "<html><head></head><body>hello_world</body></html>").
% html_parser:parse(B).

It can be a pain to build and run a parser each time we edit the source yrl file. To speed things up, we can use a program to build and run the parser for us. We compile and run the test program which builds the parser and tests it for us on some document.

-module(html_test).
-compile(export_all).

start() ->
	yecc:yecc("html.yrl","html_parser.erl"),
	cover:compile(html_parser),                         
	{_,List_of_symbols,_}=erl_scan:string(
		"<html><head><title>greeting</title></head>
			<body>
			hello there world what is up
			</body>
		</html>"),
	{ok,L} = html_parser:parse(List_of_symbols),  
	register(do_event, spawn(html_test,event_loop,[])),
	Events = lists:flatten(L),
	send_events(Events),
	Events.

send_events([]) -> do_event ! {exit};
send_events([H|T]) ->
	do_event ! H,
	%io:format(" ~w ~n",[H]),
	send_events(T).
 
event_loop() ->
	receive
		{open,{atom,_Line_Number,html}} -> 
			io:format("~n start scan ~n", []),
			event_loop();
		{contents,List} -> 
			Contents = get_contents(List,[]),
			io:format("~n contents: ~w ~n", [Contents]);
		{exit} -> exit(normal)
	end,
	event_loop().
 
get_contents([],Items) -> Items;
get_contents([H|T],Items)->
	if
		length(T) > 0 ->
			NT = hd(T);
		true ->
			NT = T
	end,
	{atom,_N,Item} = H,
	NItems = Items++[Item],
	% io:format(" ~w ",[Item]),
	get_contents(NT,NItems).
	
% 6> c(html_test).
% {ok,html_test}
% 7> html_test:start().
%  [greeting]
%  [hello,there,world,what,is,up]
% and events.


Creating Web Applications with yaws

Yaws is one of the popular web servers programmed in Erlang. The goals of Yaws is to be a feature rich web server with a multitude of options for the user to configure and deploy their web applications with.

Basic Yaws

[edit | edit source]

Elementary Yaws

[edit | edit source]

Additional Resources

[edit | edit source]



Using Erlang with Emacs

Erlang mode for emacs -http://www.erlang.org/doc/man/erlang_mode.html -http://www.erlang.org/doc/apps/tools/erlang_mode_chapter.html

You can use Distel on top of this, for extra functionality. http://code.google.com/p/distel/


Erlang Resources

  • Open Source Erlang - Official site
  • Documentation - Comprehensive documentation and user's guide for Erlang/OTP R11B
  • Erldocs - Erlang documentation alternative with filter/search function and better interface
  • trap_exit - Forum and wiki about Erlang
  • Jungerl - Collection of free Erlang software

Large Projects Built with Erlang

[edit | edit source]
  • CouchDB - Document oriented database
  • ejabberd - A large volume Jabber/XMPP IM server.
  • Wings 3D - A 3D modeller (Wikibook manual: Wings 3D)
  • wxErlang - A GUI API
  • Yaws - A web server (Wikibook manual: Yaws)
  • Disco - An implementation of the Map-Reduce framework
  • Zotonic - The Erlang Web Framewoork & CMS
[edit | edit source]
  • Erlide - An erlang IDE plugging for Eclipse