erlang: the language and the platform
DESCRIPTION
TRANSCRIPT
• Designed and implemented by Joe Armstrong in 1986
• Was originally a proprietary language within Ericsson
• Was released as open source in 1998
A New Way to Program That’s 20 Years Old
Joe Armstrong @ QCon 2009
Joe Armstrong @ QCon 2009
Erlang (BEAM) emulator version 5.5.6 [source][smp:1] [async-threads:0] [hipe] [kernel-poll:true]
Eshell V5.5.6 (abort with ^G)1>
Erlang Shell
1> 3.3
2> 1 + 2008.2009
3> 849274893217458392017483921 * 78932489321748392107483291.67035381440116040485849541847226463097212333368664011
Syntax: Number
1> atom.atom
2> anythingStartingWithLowercaseLetter. anythingStartingWithLowercaseLetter
3> 'anything enclosed in single quotes'.'anything enclosed in single quotes'
Syntax: Atom
1> true.true
2> false.false
3> is_atom(true).true
4> is_boolean(true).true
Syntax: Boolean
1> "foo"."foo"
2> "anything enclosed in double quotes"."anything enclosed in double quotes"
Syntax: String
1> [].[]
2> [ 1 ].[1]
3> [ first ].[first]
4> [ "first" ].["first"]
5> [ 1, first, "first" ].[1,first,"first"]
Syntax: List
1> [ 72, 101, 108, 108, 111 ]."Hello"
2> $H.72
3> [ $H, $e, $l, $l, $o ]."Hello"
Syntax: Strings are Lists
1> { 1, 2, 3 }.{1,2,3}
2> { name, "Gabriele" }.{name,"Gabriele"}
3> { coder, { name, "Gabriele" }, { language, "Erlang" } }.{coder,{name,"Gabriele"},{language,"Erlang"}}
Syntax: Tuple
1> <<“Hello”>>.<<“Hello”>>
2> <<1:1,8:7>>. <<136>>
3> erlang:binary_to_list(<<1:32/big>>).[0,0,0,1]
4> erlang:binary_to_list(<<1:32/little>>).[1,0,0,0]
Syntax: Binaries
1> 1 = 1.1
2> 1 = 2.** exception error: no match of right hand side value 2
3> catch 1 = 2.{'EXIT',{{badmatch,2},[{erl_eval,expr,3}]}}
Pattern Matching
1> X.* 1: variable 'X' is unbound
2> X = 5.5
3> X.5
4> X = 6.** exception error: no match of right hand side value 6
Pattern Matching: Binding Variables
1> Coder = { coder, { name, "Gabriele" } }.{coder,{name,"Gabriele"}}
2> { person, { name, "Gabriele" } } = Coder.** exception error: no match of right hand side value {coder,{name,"Gabriele"}}
3> { coder, { name, "Gabriele" } } = Coder.{coder,{name,"Gabriele"}}
Pattern Matching: Destructuring
4> { coder, { name, CoderName } } = Coder. {coder,{name,"Gabriele"}}
5> CoderName."Gabriele"
6> AnotherCoderName = "Matteo"."Matteo"
7> { coder, { name, AnotherCoderName } } = Coder.** exception error: no match of right hand side value {coder,{name,"Gabriele"}}
Pattern Matching: Destructuring
1> [ First, Last ] = [ first, last ].[first,last]
2> First.first
3> Last.last
Pattern Matching: List
4> [ Head | Tail ] = [ 1, 2, 3, 4, 5 ]. [1,2,3,4,5]
5> Head.1
6> Tail.[2,3,4,5]
Pattern Matching: List
1> [ First, Second | Others ] = [ 1, 2, 3, 4, 5 ].[1,2,3,4,5]
2> First.1
3> Second.2
4> Others.[3,4,5]
Pattern Matching: List
1> { X, Y, X } = { { abc, 12 }, 42, { abc, 12 } }.{{abc,12},42,{abc,12}}
2> X.{abc,12}
3> Y.42
Pattern Matching: In Deep
1> { X, Y, X } = { { abc, 12 }, 42, true }.** exception error: no match of right hand side value {{abc,12},42,true}
2> { X, Y, _ } = { { abc, 12 }, 42, true }.{{abc,12},42,true}
Pattern Matching: In Deep
-module(examples).-export([ hello/0 ]).
hello() -> "Hello World!".
1> c(examples).{ok,examples}
2> examples:hello()."Hello World!"
Function
examples.erl
Syntax: Punctuation
• Periods (.) end everything except when
• Semicolons (;) end clauses
• and Commas (,) separate expressions
-export([ double/1 ]).
double(0) -> 0;double(Number) -> Number * 2.
1> examples:double(0).0
2> examples:double(4).8
Function: Pattern Mathing & Clause
double(0) -> 0;double(Number) when is_integer(Number) -> Number * 2.
1> examples:double(4).8
2> examples:double(foo).** exception error: no function clause matching examples:double(foo)
Function: Guard
double(0) -> 0;double(Number) when is_integer(Number) -> Number * 2;double(AnythingElse) -> io:format("I don't know how to double ~p~n", [ AnythingElse ]), error.
1> examples:double(foo).I don't know how to double fooerror
Function: Guard
multiplyBy(Multiplier) -> fun(Number) -> Number * Multiplier end.
1> Double = examples:multiplyBy(2).#Fun<examples.0.46516809>2> Double(4).83> MultiplyBy4 = examples:multiplyBy(4).#Fun<examples.0.46516809>4> MultiplyBy4(4).16
Function: Anonymous & Closure
printList([]) -> ok;printList([ Item | List ]) -> io:format("~p~n", [ Item ]), printList(List).
1> examples:printList([ 1, 2, 3, 4 ]).1234ok
Function: Tail Recursion
foreach(_Function, []) -> ok;foreach(Function, [ Item | List ]) -> Function(Item), foreach(Function, List).
1> examples:foreach(fun(Item) -> io:format("~p~n", [ Item ])end, [ 1, 2, 3, 4 ]).1234
Function: High Order Function
foreach(Function, List) -> lists:foreach(Function, List).
1> examples:foreach(fun(Item) -> io:format("~p~n", [ Item ])end, [ 1, 2, 3, 4 ]).1234ok
Function: High Order Function
3> lists:foreach(fun(Item) -> io:format("~p~n", [ Item ])end, [ 1, 2, 3, 4 ]).1234ok
Function: High Order Function
names(Coders) -> lists:map(fun({ coder, { name, CoderName } }) -> CoderName end, Coders).
1> examples:names([ 1> { coder, { name, "Gabriele" } },1> { coder, { name, "Matteo" } } 1> ]).["Gabriele","Matteo"]
List Functions: Map
triplets(UpTo) when is_number(UpTo), UpTo >= 2 -> [ { A, B, C } || A <- lists:seq(2, UpTo), B <- lists:seq(2, UpTo), C <- lists:seq(2, UpTo), A * A + B * B =:= C * C, A + B + C =< UpTo ].
1> examples:triplets(10).[{3,4,5},{4,3,5},{6,8,10},{8,6,10}]
Syntax: List Comprehension
sum(Numbers) -> lists:foldl(fun(Number, Sum) -> Sum + Number end, 0, Numbers).
1> examples:sum([ 1, 2, 3, 4, 5 ]).15
List Functions: Fold/Reduce
sum(Numbers) -> lists:foldl(fun (Number, Sum) when is_integer(Number) -> Sum + Number; (_Number, Sum) -> Sum end, 0, Numbers).
1> examples:sum([ 1, 2, 3, foo, 4, bar, 5 ]).15
List Functions: Fold/Reduce
fizzbuzz(UpTo) -> lists:map(fun(Counter) -> if (Counter rem 15) =:= 0 -> fizzbuzz; (Counter rem 3) =:= 0 -> fizz; (Counter rem 5) =:= 0 -> buzz; true -> Counter end end, lists:seq(1, UpTo)).
1> examples:fizzbuzz(16).[1,2,fizz,4,buzz,fizz,7,8,fizz,buzz,11,fizz,13,14,fizzbuzz,16]
Example: FizzBuzz
the secret weapon
Joe Armstrong @ QCon 2009
Scalability: Concurrency
• Any function (named or anonymous) can become a process
• A process is simply a function executing in parallel
• A process shares nothing with other processes• A process is extremely lightweight: a modern
systems can accommodate literally millions of Erlang processes
1> processes:profileSpawnFor(1000). Spawned 1.000 processes in 10 (4) millisecondsok
2> processes:profileSpawnFor(10000). Spawned 10.000 processes in 96 (40) millisecondsok
3> processes:profileSpawnFor(100000).Spawned 100.000 processes in 884 (510) millisecondsok
Scalability: Concurrency
Scalability: Concurrency
• The “!” operator sends messages• Asynchronous• Order guaranted
• The “receive” statement matches messages• One call to receive, one message removed
from the process message queue• Uses pattern matching to select messages
1> Pid = spawn(fun() ->1> receive1> die -> io:format("Bye~n")1> end1> end).<0.33.0>
Scalability: Concurrency
2> is_process_alive(Pid).true
3> Pid ! die.Byedie
4> is_process_alive(Pid).false
Scalability: Concurrency
profileSpawnFor(Number) -> Processes = for(1, Number, fun() -> spawn(fun() -> wait() end) end), lists:foreach(fun(Pid) -> Pid ! die end, Processes).
wait() -> receive die -> void end.
Scalability: Concurrency
factorials(Numbers) -> map(fun(Number) -> factorial(Number) end, Numbers).
Scalability: Inherently Distributed
factorials(Numbers) -> map(fun(Number) -> factorial(Number) end, Numbers).
factorials(Numbers) ->
pmap(fun(Number) -> factorial(Number)
end, Numbers, 20).
Distributed on20 different
processes
Scalability: Inherently Distributed
factorials(Numbers) -> map(fun(Number) -> factorial(Number) end, Numbers).
factorials(Numbers) ->
pmap(fun(Number) -> factorial(Number)
end, Numbers, 20, nodes()).
Distributed on 20 different processes for each available nodes
on the cluster
Scalability: Inherently Distributed
Scalability: Inherently Distributedpmap(Map, Elements, Nodes) -> Spawn = case length(Nodes) of 0 -> fun spawn/1; Length -> NextNode = fun() -> nth(random:uniform(Length), Nodes) end, fun(F) -> spawn(NextNode(), F) end end, Parent = self(), Pids = [ Spawn(fun() -> Parent ! { self(), (catch Map(Element)) } end) || Element <- Elements ], [ receive { Pid, ReturnedValue } -> ReturnedValue end || Pid <- Pids ].
Referentially Transparent:if a function can be
replaced with its value without changing the
program
Testability: Referential Transparency
the function behavior is defined purely in terms of its input parameters
and return value.
Testability: Referential Transparency
no Las Vegas
effect!
Testability: Referential Transparency
Testability: EUnit-include_lib("eunit/include/eunit.hrl").
should_be_able_to_pick_next_player_id_test() -> NoPlayers = [], ?assertEqual(1, next_player_id(NoPlayers)), OnePlayer = [ { 1, aPlayer } ], ?assertEqual(2, next_player_id(OnePlayer)),
TwoConsecutivePlayers = [ { 1, aPlayer }, { 2, aPlayer } ], ?assertEqual(3, next_player_id(TwoConsecutivePlayers)),
TwoNonConsecutivePlayers = [ { 1, aPlayer }, { 3, aPlayer } ], ?assertEqual(2, next_player_id(TwoNonConsecutivePlayers)), ...
sector.hrl
Testability: EUnit
should_be_able_to_pick_next_player_id_test() -> ... SectorFullOfPlayers = [ { PlayerId, aPlayer } || PlayerId <- seq(1, ?MAX_NUMBER_OF_PLAYERS) ]
OnePlayerToFullSector = subtract({ 5, aPlayer }, SectorFullOfPlayers), ?assertEqual(5, next_player_id(OnePlayerToFullSector)),
?assertException(error, _, next_player_id(SectorFullOfPlayers)),
true.
sector.hrl
Testability: EUnit
next_player_id(Players) -> min( subtract( seq(1, ?MAX_NUMBER_OF_PLAYERS), [ PlayerId || { PlayerId, _Player } <- Players ] ) ).
-ifdef(?TEST).-include_lib("../test/sector.hrl").-endif.
sector.erl
Testability: EUnit
next_player_id(Players) -> min( subtract( list_of_possible_ids(), list_of_used_ids(Players) ) ).
-ifdef(?TEST).-include_lib("../test/sector.hrl").-endif.
sector.erl
Testability: Mocking Processes
a_player_cant_join_a_full_sector_test() -> ... SectorFull = spawn(?MODULE, loop, [ Status, Players ]), AskToJoinTo = fun(SectorToJoin) -> SectorToJoin ! { join, self(), aPolarity }, receive { kickout, Reason } -> { kickout, Reason }; UnexpectedResponse -> { error, unexpected, UnexpectedResponse } after 100 -> { error, timeout } end end,
?assertMatch({ kickout, sector_full }, AskToJoinTo(SectorFull)).
sector.hrl
Readability: Sometimes...
{ tcp, Socket, Message } -> case Message of ... <<16#9:8,_:64,PlayerBlasterId:16,PlayerBlastedId:16,AmountOfEnergy:8>> -> Player ! { blast, PlayerBlasterId, PlayerBlastedId, AmountOfEnergy };
<<16#A:8,_:64,FromPlayerWithId:16,ChatMessage:64/binary>> -> Player ! { chat, FromPlayerWithId, ChatMessage };
<<16#C:8,_:64,PlayerTakerId:16,EntityTakenId:16>> -> Player ! { take, PlayerTakerId, EntityTakenId }; ... end
peer.erl
Readability: Sometimes...
<<16#9:8,_:64,PlayerBlasterId:16,PlayerBlastedId:16,AmountOfEnergy:8>> -> Player ! { blast, PlayerBlasterId, PlayerBlastedId, AmountOfEnergy };
peer.erl
! Blast!! Request to Blast a Player (blast)|1:length|1:0x9|8:timestamp|2:blasterId|2:blastedId|1:amountOfEnergy|
protocol.md
Reliability: Fault Tollerance is Easier
• The “link” function can link processes• When a process die the linked processes receive
a message { ‘EXIT’, Died, Reason } • A monitor process could respawn the died ones
or cleanup the system
-module(killer).-export([ start/0 ]).
start() -> Victim = spawn(fun victim/0), Killer = spawn(fun killer/0), Witness = spawn(fun witness/0),
Witness ! { watch, Victim }, Killer ! { kill, Victim }.
killer.erl
Reliability: Fault Tollerance is Easier
killer() -> receive { kill, Victim } -> io:format("killer: I will kill you!!!~n"), Victim ! die end.
victim() -> receive die -> io:format("victim: Ahhhhhh!!!~n") end.
killer.erl
Reliability: Fault Tollerance is Easier
witness() -> receive { watch, Victim } -> process_flag(trap_exit, true), link(Victim), witness(); { 'EXIT', _WhoDied, _WhyHeDied } -> io:format("witness: Help!!!~n") end.
killer.erl
Reliability: Fault Tollerance is Easier
1> c(killer). {ok,killer}
2> killer:start().killer: I will kill you!!!victim: Ahhhhhh!!!witness: Help!!!
Reliability: Fault Tollerance is Easier
Reliability: Fault Tollerance is Easier
PeerSoc
ket
Player
1
Sector
1Universe
PeerPlayer
2
PeerPlayer
3
PeerPlayer
4
Reliability: Fault Tollerance is Easier
PeerSoc
ket
Player
1
Sector
1Universe
Player
2
Player
3
Watcher
Link Link
Link
Link
Link
Link
Reliability: Fault Tollerance is Easier
PeerSoc
ket
Player
1
Sector
1
Player
2
Player
3
Watcher
{ ‘EXIT’, Peer }
{ leave, Player }
{ leaved, Player }
{ leave, Player }
Reliability: Fault Tollerance is Easier
Player
1
Sector
1
Player
2
Player
3
Watcher
Peer
Peer
Peer
Universe
{ ‘EXIT’, Sector, _ }
{ remove, Sector }
{ leaved, Player }
{ lea
ved,
... }
Reliability: Fault Tollerance is Easier
Scalability: Show me some Numbers!ping-pong on EC2
http://github.com/gabrielelana/ping-pong
Scalability: Show me some Numbers!5 messages/second
Pingers ErlangRTD/Median
ErlangRTD/Deviation
ErlangErrors
JavaRTD/Median
JavaRTD/Deviation
JavaErrors
50 0.1 0.35 0 0.1 0.69 0
100 0.24 0.41 0 0.17 0.4 0
500 0.73 0.7 0 2.96 2.8 0
1000 0.65 6.5 0 12.7 10.10 0
5000 6.17 27.7 0 1007.37 908.32 0
10000 49.34 31.4 51 20372.6 6451.46 1912
Scalability: Show me some Numbers!10 messages/second
Pingers ErlangRTD/Median
ErlangRTD/Deviation
ErlangErrors
JavaRTD/Median
JavaRTD/Deviation
JavaErrors
50 0.64 0.39 0 0.2 0.67 0
100 0.74 0.46 0 0.5 1.97 0
500 1.5 5.36 0 4.03 7.44 0
1000 1.81 10.6 0 32.22 125.43 0
5000 46.17 46.46 3 1204.8 3674.6 314
Scalability: Show me some Numbers!20 messages/second
Pingers ErlangRTD/Median
ErlangRTD/Deviation
ErlangErrors
JavaRTD/Median
JavaRTD/Deviation
JavaErrors
50 0.9 0.8 0 0.4 0.77 0
100 1.6 11.35 0 1.4 14.95 0
500 4.92 11.06 0 5.67 15.41 6
1000 11.43 25.18 0 159.36 278.2 2
5000 51.72 54.19 280 462.31 223.1 1400
Why Erlang?
Damien Katz @ Ruby Fringe 2008
... no silver bullets: Weaknesses
• Flat namespaces• Obscure documentation • Few mytical examples of success• Very bad I/O performance• Vary bad string manipulation performance• Lack of libraries and code examples• No strong IDE and tools support
Hard Gained Insights
• Don’t miss tail-call recursion• Asynchronous is good• Small functions are good• When in doubt create a new process• Think early what to do when a process die
Some books are coming out