Erlang Programming/Printable version
This is the print version of Erlang Programming You won't see this message or any elements not part of the book's content when you print or preview this page. |
The current, editable version of this book is available in Wikibooks, the open-content textbooks collection, at
https://en.wikibooks.org/wiki/Erlang_Programming
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]- ↑ Audio interview with Joe Armstrong from (http://www.se-radio.net)
- ↑ 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
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.
Unix
[edit | edit source]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
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.
Pids
[edit | edit source]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>
Refs
[edit | edit source]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
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]- ↑ 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
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
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
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]
sort
[edit | edit source]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
ETS
[edit | edit source]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]
This page or section is an undeveloped draft or outline. You can help to develop the work, or you can ask for assistance in the project room. |
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
Erlang related projects
[edit | edit source]- Erlide - An erlang IDE plugging for Eclipse