CPL 2016, week 8 Erlang functional core and agents Oleg Batrashev - - PowerPoint PPT Presentation
CPL 2016, week 8 Erlang functional core and agents Oleg Batrashev - - PowerPoint PPT Presentation
CPL 2016, week 8 Erlang functional core and agents Oleg Batrashev Institute of Computer Science, Tartu, Estonia March 28, 2016 Overview Today Erlang: functional core and agents Next week Erlang fault tolerance and distributed
Overview
Today
◮ Erlang: functional core and agents
Next week
◮ Erlang fault tolerance and distributed programming
Functional core 3/26
- Shell and statements
◮ statements must end with a dot X=3+5. ◮ Erlang shell erl
N> user input
- utput
1> 5 + 4. 9 2> 7 + 8*4. 39
Functional core 4/26
- Erlang variables
◮ start with uppercase letter Var=5. ◮ single assignment: X=5. X=4. → exception
◮ bound/unbound ◮ X=2+2. X=4. is ok
◮ = is a pattern matching operator (Lhs = Rhs)
◮ X and Y are assigned in {X, _, Y}= Z
◮ almost everything in this lecture is declarative: structures are
immutable, etc, except:
◮ io:format is not because it has side effects ◮ mailboxes in the next part of the lecture introduce
non-determinism
Functional core 5/26
- Types and values
◮ atoms (symbolic constants): X=monday. Ret=ret_success.
◮ start lowercase or within single quotes: 'Monday'
◮ tuples: Point={point, 10, 45}.
◮ extracting {point, _, Y}=Point.
◮ lists: L=[10,6,4].
◮ extracting [H|T]=L. results in H=10. L=[6,4].
◮ strings are lists of integers: [104,97,112|_]="happy".
◮ may use dollar sign 104=$h.
Functional core 6/26
- Records
◮ declare, only in .hrl file:
- record(todo, {status=remind,who="oleg",text}).
◮ label, {key1 = default1, key2, . . . }
◮ load declaration to shell: rr("myrecords.hrl"). ◮ create: X1=#todo{text="lecture slides"}. ◮ update: X2=X1#todo{status=done}.
◮ declarative - new record is created
◮ extract: #todo{who=W, text=Txt}=X2. ◮ records are syntactic convinience - tuples are behind the scene
◮ rf(todo).X2. shows it as {todo,done,"oleg","lecture slides"}
Functional core 7/26
- Modules
- module(geometry ).
- export([area /1]).
area({rectangle , Width , Hgt}) -> Width*Hgt; area({circle , R}) -> 3.14159*R*R. ◮ function consists of clauses, separated by semicolon
◮ case with matched pattern is executed (like in Haskell)
◮ compile (geometry.erl) and run 77> c(geometry ). {ok ,geometry} 78> geometry:area({rectangle ,10 ,5}). 50 79> geometry:area({circle ,1.4}). 6.157516399999999
Functional core 8/26
- Functions
◮ functions with same name but different arity (number of
arguments) are different (cost/1, cost/2,..)
- module(shop ).
- export([cost /1]).
cost(oranges) -> 5; cost(newspaper) -> Cost = 8, io:format("newspaper cost is ~p~n", [Cost]), Cost; cost(apples) -> 2. ◮ clauses are matched top-down
◮ semicolon (;) separates clauses ◮ period (.) separates functions (and statements in erlang shell) ◮ comma (,) separates statements inside function
Functional core 9/26
- Iteration
◮ Like in other functional languages its recursion
- module(shop ).
- export([cost/1, total /1]).
... total([]) -> 0; total([H|T]) -> cost(H)+ total(T). ◮ test it in shell (f() – forgets all definitions) 98> f().
- k
99> c(shop ). {ok ,shop} 100> ShopList=[oranges ,newspaper]. [oranges ,newspaper] 101> shop:total(ShopList ). newspaper cost is 8 13
Functional core 10/26
- Funs and list processing
◮ Funs (anonymous functions) IsEven=fun(X) -> (X rem 2) =:= 0 end.
◮ stored to IsEven variable ◮ called by IsEven(23).
◮ map, filter, reduce 104> C = lists:map(fun(X) -> shop:cost(X) end , ShopList ). newspaper cost is 8 105> lists:filter(IsEven ,C). "\b" 106> lists:foldl(fun(S,X)->S+X end , 0, C). 13
Functional core 11/26
- Defining abstractions
◮ there is no for loop in Erlang, in lib_misc.erl
- module(lib_misc ).
- export([for /3]).
for(Max , Max , F) -> [F(Max)]; for(I, Max , F) -> [F(I)| for(I+1, Max , F)].
◮ two arguments Max must be equal for the first match to
succeed
◮ test it in shell 119> c(lib_misc ). {ok ,lib_misc} 120> lib_misc:for (1,10, fun(I)->I*I end ). [1 ,4 ,9 ,16 ,25 ,36 ,49 ,64 ,81 ,100]
Functional core 12/26
- List comprehension
◮ make programs shorter by sugaring funs, maps and filters ◮ lets L=[1,2,3,4,5].
◮ map: [2*X || X <- L]. ◮ with filter: [2*X || X <- L, X<3].
◮ Example: qsort qsort([]) -> []; qsort([Pivot|T]) -> qsort([X || X <- T, X < Pivot]) ++ [Pivot] ++ qsort([X || X <- T, X >= Pivot]).
Functional core 13/26
- Guards
◮ increase the power of pattern matching max(X,Y) when X > Y -> X; max(X,Y) -> Y. ◮ guard sequence is a series of guards separated by semicolon
◮ at least one must be true
◮ guard is a series of guard expressions separated by comma
◮ all must be true
◮ valid guard expressions are restricted to a subset of Erlang
expressions!
◮ because we want them to be side effect free
Functional core 14/26
- Case and if expressions
◮ case has the following syntax case Expression
- f
Pattern1 [when Guard1] -> Expr_seq1; Pattern2 [when Guard2] -> Expr_seq2; .... end ◮ if expression syntax if Guard1
- > Expr_seq1;
Guard2
- > Expr_seq2;
... end
Functional core 15/26
- Accumulators
◮ carry values in function arguments trough recursive calls ◮ Example: split a list into odd and even values
- dds_evens_acc (L) ->
- dds_evens_acc (L, [], []).
- dds_evens_acc ([H|T], Odds , Evens) ->
case (H rem 2) of 1 -> odds_evens_acc (T, [H|Odds], Evens ); 0 -> odds_evens_acc (T, Odds , [H|Evens]) end;
- dds_evens_acc ([], Odds , Evens) ->
{Odds , Evens}. ◮ test 136> lib_misc: odds_evens_acc ([1,4,5,4,5,4]). {[5,5,1],[4,4,4]}
Agents 16/26
- Concurrency primitives
◮ declarative Erlang + recursive functions with mailboxes ◮ primitives
◮ create a new concurrent process that runs Fun
Pid = spawn(Fun)
◮ send Message to the process with identifier Pid
Pid ! Message
◮ receive a message that has been sent to a process
receive Pattern1 [when Guard1] -> Expressions1 ; Pattern2 [when Guard] -> Expressions2 ; ... end
Agents 17/26
- Processes and mailboxes
◮ every process has one mailbox ◮ when a message arrives and the process is waiting in receive
it tries to match it against patterns
◮ if none succeeds ◮ the message is saved for later processing ◮ next message is tried
◮ it behaves like reading queue with sophisticated receive routine
◮ one thread processing messages, one at a time ◮ Pid !
Message is a source of non-determinism
◮ standard queue does not save anything for later processing
Agents 18/26
- Simple example
−module ( area_server0 ) . −export ( [ loop /0 ] ) . loop () − > r e c e i v e { r e c t a n g l e , Width , Ht} − > i o : format ( " Area
- f
r e c t a n g l e i s ~p~n" , [ Width ∗ Ht ] ) , loop ( ) ; { c i r c l e , R} − > i o : format ( " Area
- f
c i r c l e i s ~p~n" , [ 3.14159 ∗ R ∗ R] ) , loop ( ) ; Other − > i o : format ( " I don ' t know about ~p ~n" , [ Other ] ) , loop () end .
Agents 19/26
- Test simple example
137> c( area_server0 ). {ok , area_server0 } 138> Pid = spawn(fun area_server0 :loop /0). <0.299.0 > 139> Pid ! {rectangle , 6, 10}. Area of rectangle is 60 {rectangle ,6 ,10} 140> Pid ! {circle , 23}. Area of circle is 1661.90111 {circle ,23} 141> Pid ! {triangle ,2,4,5}. I don 't know about {triangle ,2,4,5} {triangle ,2,4,5}
Agents 20/26
- Client-server example
◮ lets send extended message to the server Pid ! {self(), {rectangle ,6 ,10}}
◮ self() is the PID of the client process
◮ the server code is now loop () -> receive {From , {rectangle , Width , Ht}} -> From ! Width*Ht , loop (); ...
◮ i.e. we respond with the answer – the client must wait for a
response (this is like Remote Procedure Call)
Agents 21/26
- RPC (1)
◮ RPC: a client may execute receive just after it sends a
message and wait for the answer
rpc(Pid , Request) -> Pid ! {self(), Request}, receive Response
- > Response
end.
◮ the problem: any incoming message may be wrongly
considered as a response
Agents 22/26
- RPC (2)
◮ solution: may pattern match on server Pid to filter out other
messages (notice Pid is already bound)
rpc(Pid , Request) -> Pid ! {self(), Request}, receive {Pid ,Response} -> Response end.
◮ matching succeeds if server sends the same Pid
◮ server code: loop () -> receive {From , {rectangle , Width , Ht}} -> From ! {self(), Width*Ht}, loop (); ...
Agents 23/26
- Receive with timeout
◮ a message may never come, e.g. because of error, break after
Time has passed
receive Pattern1 [when Guard1] -> Expressions1 ; ... after Time
- >
Expressions end ◮ receive with timeout 0 is like polling
◮ before terminating checks the mailbox ◮ may use for priority receive
receive {alarm , X} -> ... after 0 -> receive Any
- > ... end
end.
Agents 24/26
- How “receive” works
- 1. upon enter a receive statement timer is started (if after section
is present)
- 2. take the first message, try to match all patterns
◮ on success remove the message, execute statements
- 3. if none matches put it to “save queue”, try next, etc.
- 4. if no more messages that match then suspend until
◮ only new/arrived messages are tried
- 5. as soon as one matches:
◮ all “save queue” is reentered to the mailbox ◮ timer is cleared
- 6. if timer elapses then execute corresponding section:
◮ all “save queue” is reentered to the mailbox
Agents 25/26
- Registered processes
◮ it may be more convenient to register a process globally
instead of passing Pid around
◮ the functions:
◮ register(AnAtom,Pid) – register with the name AnAtom ◮ unregister(AnAtom) ◮ whereis(AnAtom) -> Pid | undefined ◮ registered() -> [AnAtom::atom()] – return the list of all
registered processes
Agents 26/26
- Tail recursion and performance
◮ all loop() calls come as the last expression
◮ if not then stack grows and indefinite looping (agent life) is
not possible
◮ processes are “user space threads”, i.e. not native threads
◮ cheap without much overhead ◮ Erlang has a limit of 32767 processes but it may be