This article describes various properties of Erlang programming language, preferably with a short code example.
Erlang supports higher order functions, that is, functions which accept functions as argument or return functions as return value. In Erlang functions are first class citizens and are treated similar to integers, floats or strings. (Note: Soon recursive anonymous functions would be supported.)
Example:1> lists:map(fun(X) -> X*X end, lists:seq(1,5)). [1,4,9,16,25] 2> lists:map(fun(X) -> (X rem 2) =:= 0 end, lists:seq(1,5)). [false,true,false,true,false] 3> lists:filter(fun(X) -> (X rem 2) =:= 0 end, lists:seq(1,5)). [2,4] 4> lists:sum(lists:seq(1,5)). 15 5> lists:foldl(fun(X, Y) -> X + Y end, 0, lists:seq(1,5)). 15 6>
Similar to most functional languages Erlang supports exact integer arithmetic. Thus programmers do not have to worry about overflow errors.
Example:1> 999999999999999 * 999999999999999. 999999999999998000000000000001 2> 9999999999999999999999999 rem 2. 1 3> 9999999999999999999999999 div 2. 4999999999999999999999999 4>
Erlang is dynamically typed. Thus programmers do not need to declare type of the variables. Note that type checking using success types is possible using dialyzer, which can be further augmented by -spec() declarations.
Example:1> A=fun(X, Y) -> X + Y end. #Fun<erl_eval.12.107821302> 2> A(1, 2). 3 3> A(1.1, 2.2). 3.3000000000000003 4>
Erlang supports guards in function definition, case, try-catch etc. which make code more reliable and easier.
Example:1> A=fun(X, Y) when is_integer(X), is_integer(Y) -> X div Y end. #Fun<erl_eval.12.107821302> 2> A(3, 2). 1 3> A(10, 7). 1 4> A(20, 7). 2 5> A(10.1, 7). ** exception error: no function clause matching erl_eval:'-inside-an-interpreted-fun-'(10.1,7) 6> A(10, 7.1). ** exception error: no function clause matching erl_eval:'-inside-an-interpreted-fun-'(10,7.1) 7>
Strong match syntax support in function declaration, statements, receive, try-catch, etc. makes code compact and easy to write.
Example:1> [{A, B}, C, [D, E]] = [{a, b}, c, [d, e]]. [{a,b},c,[d,e]] 2> A. a 3> [A, B, C, D, E]. [a,b,c,d,e] 4>
It is not possible to change binding (value) of identifiers (variables) in Erlang. In fact '=' is not an assignment operator. It is a match operator.
Example:1> X=1. 1 2> X=2. ** exception error: no match of right hand side value 2 3>
Erlang supports spawn() function for creating new processes as part of language without requiring use of external library. This makes it very easy to write concurrent applications in Erlang. Further applications are largely free from dead-lock and race conditions due to immutable memory property as there is no need for a critical section.
Example:1> Self=self(). <0.32.0> 2> spawn(fun() -> Self ! 3 * 4 end). <0.35.0> 3> flush(). Shell got 12 ok 4>
Erlang processes communicate only via pure message passing. Shared memory is not supported. Note that other mechanisms for processes to share information such as ets, dets, mnesia, files or sockets are present. If these mechanisms are used then in most cases isolation and atomicity options are provided or are inherently available due to pure message passing.
Example:1> ets:new(table1, [set, protected, named_table]). table1 2> ets:insert(table1, {hello}). true 3> ets:lookup(table1, hello). [{hello}] 4> ets:insert(table1, {hello, 5}). true 5> ets:lookup(table1, hello). [{hello,5}] 6> Self=self(). <0.32.0> 7> spawn(fun() -> Self ! ets:lookup(table1, hello) end). <0.41.0> 8> flush(). Shell got [{hello,5}] ok 9>
Erlang makes it very easy to write distributed programs due to following properties:
-module(adder). -compile(export_all). adder() -> receive {add, X, Y, Sender} -> Sender ! {sum, X+Y}, adder(); stop -> ok end. start() -> Pid1=spawn(fun adder/0), register(adder, Pid1), ok. add(Pid1, X, Y) -> Self=self(), Pid1 ! {add, X, Y, Self}, receive {sum, Sum1} -> Sum1 end, Sum1.Steps performed on node1:
[saurabh@barjatiyacc about_erlang]$ erl -sname node1 Erlang R14B04 (erts-5.8.5) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false] Eshell V5.8.5 (abort with ^G) (node1@barjatiyacc)1> c(adder). {ok,adder} (node1@barjatiyacc)2> adder:start(). ok (node1@barjatiyacc)3> adder:add(adder, 3, 4). 7 (node1@barjatiyacc)4> whereis(adder). <0.45.0> (node1@barjatiyacc)5>Steps performed on node2:
[saurabh@barjatiyacc about_erlang]$ erl -sname node2 Erlang R14B04 (erts-5.8.5) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false] Eshell V5.8.5 (abort with ^G) (node2@barjatiyacc)1> Pid1=rpc:call('node1@barjatiyacc', erlang, whereis, [adder]). <6515.45.0> (node2@barjatiyacc)2> Pid1 ! {add, 3, 4, self()}. {add,3,4,<0.38.0>} (node2@barjatiyacc)3> flush(). Shell got {sum,7} ok (node2@barjatiyacc)4> rpc:call('node1@barjatiyacc',adder,add,[adder, 5, 7]). 12 (node2@barjatiyacc)5> process_flag(trap_exit, true). false (node2@barjatiyacc)6> link(Pid1). true (node2@barjatiyacc)7> Pid1 ! stop. stop (node2@barjatiyacc)8> flush(). Shell got {'EXIT',<6515.45.0>,normal} ok (node2@barjatiyacc)9>
A database server written in Erlang which supports all erlang data types (Lists, Tuples, etc.) directly is available. Further the database supports distribution across nodes for scalability and redundancy. It is possible to start database only in RAM to use it as transactional memory.
Example (Uses records from mnesia.hrl):1> rr("mnesia.hrl"). [student] 2> #student{}. #student{id = undefined,roll_no = undefined, name = undefined,email = undefined} 3> A1=#student{id=1, roll_no=200899004, name="Saurabh Barjatiya", email="saurabh@sbarjatiya.com"}. #student{id = 1,roll_no = 200899004, name = "Saurabh Barjatiya",email = "saurabh@sbarjatiya.com"} 4> mnesia:start(). ok 5> mnesia:create_table(student, [{attributes, record_info(fields, student)}, {index, [roll_no]}, {type, set}]). {atomic,ok} 6> F1=fun() -> mnesia:write(A1) end. #Fun<erl_eval.20.21881191> 7> Query1=qlc:q([X || X<-mnesia:table(student)]). {qlc_handle,{qlc_lc,#Fun<erl_eval.20.21881191>, {qlc_opt,false,false,-1,any,[],any,524288,allowed}}} 8> F2=fun() -> qlc:e(Query1) end. #Fun<erl_eval.20.21881191> 9> mnesia:transaction(F2). {atomic,[#student{id = 1,roll_no = 200899004, name = "Saurabh Barjatiya", email = "saurabh@sbarjatiya.com"}]} 10> A2=A1#student{name="Other student"}. #student{id = 1,roll_no = 200899004,name = "Other student", email = "saurabh@sbarjatiya.com"} 11> F3=fun() -> mnesia:write(A2) end. #Fun<erl_eval.20.21881191> 12> mnesia:transaction(F3). {atomic,ok} 13> mnesia:transaction(F2). {atomic,[#student{id = 1,roll_no = 200899004, name = "Other student",email = "saurabh@sbarjatiya.com"}]} 14>
A very scalable web server written purely in Erlang is available. This again allows message passing from web server or web applications to other applications without impedance mismatch. An online Erlang interpreter which uses this to its advantage is available here.
Example:This web page is being served using yaws :)
A large number of tools and libraries which make it easy to program using erlang are available such as:
Most of these tools support concurrent and distributed programs. Thus debugging or type checking distributed programs is equally easy using same tools which can be used for debugging sequential programs.
Code written in Erlang is very concise and it is pleasure to read it. Most the times there is one-to-one correspondence between Erlang code and algorithm explained in English, provided enough supporting functions are available for necessary abstraction.
Example factorial module:-module(factorial). -compile(export_all). factorial(0) -> 1; factorial(N) -> N * factorial(N-1).Example usage of factorial module:
1> c(factorial). {ok,factorial} 2> factorial:factorial(100). 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 3>
List comprehension make it very easy to write both map and filter operations in very easy and concise manner.
Example quicksort.erl module written using list comprehensions is:-module(quicksort). -compile(export_all). sort([]) -> []; sort([H | T]) -> lists:append( [sort([X || X<-T, X=Example usage of quicksort module:=H]) ] ).
1> c(quicksort). {ok,quicksort} 2> quicksort:sort(lists:map(fun(_) -> random:uniform(1000) end, lists:seq(1,10))). [93,312,444,478,502,598,667,724,916,946] 3>
Escript allows one to write directly usable scripts in Erlang which do not require manual compilation to beam code for execution.
Example hello_world.escript script:#!/usr/bin/env escript main(_) -> io:format("Hello world~n"), ok.Example invocation of hello_world escript:
[saurabh@barjatiyacc about_erlang]$ ./hello_world.escript Hello world [saurabh@barjatiyacc about_erlang]$
Similar to most functional languages lists ([1, 2, 3]) and tuples ({1, 2, 3}) are available as primitive data types. This makes it very easy to write programs which deal with collections without requiring external libraries or effort in implementing complex data structures.
Example:1> {ok, A1} = file:read_file("mnesia.hrl"). {ok,<<"-record(student, {id, roll_no, name, email}).\n">>} 2> A2=binary_to_list(A1). "-record(student, {id, roll_no, name, email}).\n" 3> A3=string:tokens(A2, "-(, {}).\n"). ["record","student","id","roll_no","name","email"] 4> length(A3). 6 5> lists:usort(A3). ["email","id","name","record","roll_no","student"] 6> lists:reverse(A3). ["email","name","roll_no","id","student","record"] 7>
We can spawn hundred thousand or more processes. Erlang uses only 200-300MB RAM in creating and keeping these many processes blocked on receive.
Example (Start erlang using something like 'erl +P 10000000'):1> Pid_list=lists:map(fun(_) -> spawn(fun() -> receive stop -> ok end end) end, lists:seq(1,100000)). [<0.34.0>,<0.35.0>,<0.36.0>,<0.37.0>,<0.38.0>,<0.39.0>, <0.40.0>,<0.41.0>,<0.42.0>,<0.43.0>,<0.44.0>,<0.45.0>, <0.46.0>,<0.47.0>,<0.48.0>,<0.49.0>,<0.50.0>,<0.51.0>, <0.52.0>,<0.53.0>,<0.54.0>,<0.55.0>,<0.56.0>,<0.57.0>, <0.58.0>,<0.59.0>,<0.60.0>,<0.61.0>,<0.62.0>|...] 2> erlang:memory(). [{total,359185296}, {processes,353083912}, {processes_used,353070992}, {system,6101384}, {atom,456137}, {atom_used,429016}, {binary,34784}, {code,3458308}, {ets,254144}] 3> length(erlang:processes()). 100025 4> lists:map(fun(Pid1) -> Pid1 ! stop end, Pid_list). [stop,stop,stop,stop,stop,stop,stop,stop,stop,stop,stop, stop,stop,stop,stop,stop,stop,stop,stop,stop,stop,stop,stop, stop,stop,stop,stop,stop,stop|...] 5> length(erlang:processes()). 25 6> erlang:memory(). [{total,182056856}, {processes,175965336}, {processes_used,106352416}, {system,6091520}, {atom,456945}, {atom_used,429645}, {binary,14056}, {code,3467257}, {ets,255144}] 7>
Since erlang processes can monitor each other a very reliable framework which allows programmers to monitor their programs (servers) such that if programs crash they can be restarted is part of Erlang as OTP. A very reliable switch AXD 301 which has about a million lines of Erlang code is said to have nine nines of availability. (About 1 second downtime in 40 years).
The interpreter application available here uses OTP internally.
Very reliable and scalable distributed databases such as Riak and CouchDB are built using Erlang. This makes Erlang very good choice for big data applications. The advantages of using Riak and CouchDB from Erlang is same as those for using Mnesia or yaws.
Interpreter application available here uses mnesia to persistently store environment bindings for users between yaws server restarts. This allows me to restart yaws on server without worrying about a connected user loosing his session information between reboots.
Erlang supports binaries and supports matching of bits. This makes it very suitable for network protocol programming, image processing, reading encoded files, etc.
Example:1> Red1=30, Green1=15, Blue1=6. 6 2> Color16_1 = <<Red1:5, Green1:6, Blue1:5>>. <<"ñæ">> 3> <<Red2:5, Green2:6, Blue2:5>>=Color16_1. <<"ñæ">> 4> {Red2, Green2, Blue2}. {30,15,6} 5> Binary1 = <<1, 2, 3, 6, 7.8/float>>. <<1,2,3,6,64,31,51,51,51,51,51,51>> 6> Binary2 = <<$a, $b, Binary1:2/binary, $c, $d>>. <<97,98,1,2,99,100>> 7> Binary3 = <<"Hello", "World", <<32, 97, 98, 99>>:2/binary >>. <<"HelloWorld a">> 8> Binary4s = {<<1:32/big>>, <<1:32/little>>, <<1:32/native>>,<<1:32>>}. {<<0,0,0,1>>,<<1,0,0,0>>,<<1,0,0,0>>,<<0,0,0,1>>} 9> Binary5s = {<<-2:32/signed>>, <<-2:32/unsigned>>, <<66535:16/signed>>, <<66535:16/unsigned>>}. {<<"ÿÿÿþ">>,<<"ÿÿÿþ">>,<<3,231>>,<<3,231>>} 10>
Erlang has very strong support for TCP/IP sockets. This makes it very good choice for building network applications. One unique feature of erlang is support for active once sockets where a socket automatically becomes inactive after receiving data. This way socket gets blocked and does not accepts more information unless application has consumed existing information. This protects application from getting flooded with data without having to write lot of code to enable/disable socket as part of application logic.
All erlang processes talk to each other using message passing. The messages received by each process are queued in message queue till process uses receive call to retrieve messages from its queue. The receive function supports match to allow matching if specific messages first allowing messages to be processed in different order in comparison to order in which they were received. receive also supports timeout so that if there is no matching message, program can do something else instead of remaining blocked forever.
Example:1> self() ! 1. 1 2> self() ! 2. 2 3> receive 3 -> 3 after 0 -> empty_queue end. empty_queue 4> receive 2 -> 2 after 0 -> empty_queue end. 2 5> receive 2 -> 2 after 0 -> empty_queue end. empty_queue 6> receive 1 -> 1 after 0 -> empty_queue end. 1 7> receive 1 -> 1 after 0 -> empty_queue end. empty_queue