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 programming
Functional core 3/26 - Shell and statements ◮ statements must end with a dot X=3+5. ◮ Erlang shell erl � N � > � user input � � output � 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 � , {� key 1 � = � default 1 � , � key 2 � , . . . } ◮ 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(). ok 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 of 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 odds_evens_acc (L) -> odds_evens_acc (L, [], []). odds_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; odds_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 of 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 of 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.
Recommend
More recommend