Lecture 10 Erlang
Erlang Functional Concurrent Distributed Soft real-time OTP (fault-tolerance, hot code update ) Open Check the source code of generic behaviours 2
Functional Haskell call-by-need (lazy) Expression are evaluated just-in-time Hard to predict memory and execution behaviour Erlang call-by-value (strict) Combinator style OK Direct pattern matching tail recursion Process tail recursion Constant memory usage 3
Recursion -module(list_stuff). -export([append/2, reverse/1]). append([x Xs], Ys) -> [X append(xs, Ys)]; average([], Ys) -> Ys. reverse([h T]) -> append(reverse(t), [H]); reverse([]) -> []. 4
Tail Recursion -module(list_stuff). -export([reverse/1, append/2]). reverse(xs) -> reverse_a(xs, []). reverse_a([x Xs], Acc) -> reverse_a(xs, [X Acc]); reverse_a([], Acc) -> Acc. %%continues 5
Tail Recursion %%continuation append(xs, Ys) -> reverse_a(r?verse(xs),ys). reverse_a(reverse(xs),ys). 6
Concurrent Programming Based on Message Passing: Q. What form of synchronisation? A. Asynchronous Q. What form of process naming? A. Direct, asymmetric A send MSG to B B receive anything 7
Asynchronous Message Passing Asynchronous send, receive from mailbox send receive Pid! Msg receive Msg-> receive Msg-> Pid! Msg 8
Concurrent Spawn a function evaluation in a separate thread Callout Pid = spawn( fun() -> loop(42) end) Function must use constant space Tail recursive fun() -> loop(42) end 9
Receive Message Order A receive statement tries to find a match as early in the mailbox as it can msg_1 msg_2 msg_1 msg_3 msg_2 msg_4 S msg_4 S receive msg_3 -> end 10
Receive Message Order A receive statement tries to find a match as early in the mailbox as it can msg_1 msg_2 msg_1 msg_4 S msg_4 S receive msg_4 -> msg_2 -> end 11
Receive Message Order A receive statement tries to find a match as early in the mailbox as it can msg_1 msg_2 msg_2 msg_4 S msg_4 S receive msg_4 -> _ -> end 12
Client-Server Interaction Common asynchronous communication pattern For example: a web server handles requests for web pages from clients (web browsers) client {request, Reg} client {result, Rep} server client client 13
Modelling Client-Server Computational server Off-loading heavy mathematical operations For example: factorial -module(math_server). -import(math_stuff,[factorial/1]). -export([start/0]). start() -> spawn(fun() -> loop(0) end). %%continues 14
Main Server Loop %%continuation loop(count) -> receive {factorial, From, N} -> Result = factorial(n), From! {result, Result}, loop(count+1); {get_count, From} -> From! {result, Count}, loop(count); stop -> true end. 15
Client Interface Encapsulating client access in a function Private channel for receiving replies? %%possible continuation compute_factorial(pid, N) -> Pid!{factorial, self(), N}, receive {result, Result} -> Result end. 16
References Private channel for receiving replies? Find the corresponding reply in the mailbox receive {result, Result} -> Result end BIF make_ref() Provides globally unique object different from every other object in the Erlang system including remote nodes 17
RPC Client Interface References to uniquely identify messages References allow only matching %%usual continuation compute_factorial(pid, N) -> Ref = make_ref(), Pid!{factorial, self(), Ref, N}, receive {result, Ref, Result} -> Result end. 18
RPC Main Server Loop %%continuation loop(count) -> receive {factorial, From, Ref, N} -> Result = factorial(n), From! {result, Ref, Result}, loop(count+1); {get_count, From, Ref} -> From! {result, Ref, Count}, loop(count); stop -> true end. 19
Registered Processes register(atom(), pid()) -> true BIF start() -> Pid = spawn(fun() -> loop(0) end), register(math_server, Pid), Pid. sending to a registered process math_server!{factorial, self(), 100}. 20
A Generic Server Desired features Proper reply behaviour - RPC Parameterised by the engine F Allows the engine to be upgraded dynamically Robust: does not crash if the engine goes wrong 21
More Generic Client-Server Higher-order functions can be used to capture the design pattern of a clientserver in a single module The main engine of a server has type : F:(State,Data) -> {Result,NewState} The state of the server before computing the query The new state of the server after the query 22
Main Server Loop loop(state, F) -> receive {request, From, Ref, Data} -> {R, NS} = F(State, Data), From! {result, Ref, R}, loop(ns, F); {update, From, Ref, NewF} -> From! {ok, Ref}, loop(state, NewF); stop -> true end. 23
Exceptions Expression evaluation can fail Examples: Bad pattern matching ({} = {1}) **exited: {{badmatch,{1}},[{erl_eval,expr,3}]}** Arithmetic error (1/0). **exited: {badarith, }** Using catch expression catch(1/0) = {'EXIT',{badarith, }} catch(1/1) = 1 24
More Robust Main Server Loop loop(state, F) -> receive {request, From, Ref, Data} -> case catch(f(state, Data)) of {'EXIT', Reason} -> From!{exit, Ref, Reason}, loop(state, F); {R, NewState} -> From!{result, Ref, R}, loop(newstate, F) end; 25
RPC Client Interface Generic client with exception passing request(pid, Data) -> Ref = make_ref(), Pid!{request, self(), Ref, Data}, receive {result, Ref, Result} -> Result; {exit, Ref, Reason} -> exit(reason) end. 26
Generic Server Instance -module(math_server). -import(math_stuff,[factorial/1]). -export([start/0]). start() -> g_server:start(math_server, 0, fun fx/2). fx(count, {factorial, N}) -> Result = factorial(n), {Result, Count+1}; fx(count, get_count) -> {Count, Count}. 27
Generic Server Instance Encapsulated client interface compute_factorial(pid, N) -> g_server:request(pid, {factorial, N}). get_count(pid) -> g_server:request(pid, get_count). 28
Classic Exercise Expressive power? Asynchronous message passing Yes, we know this already But this time we have direct naming Does this complicate matters? Somewhat. 29
Semaphore as Asynchronous Channel Semaphore sem s = N P(s); V(s); op void s(); for(int x=0;x<n;x++) send s(); receive s(); send s(); Channel 30
Modelling Semaphores A process has to simulate a semaphore -module(semaphore). -export([make/1, p/1, v/1]). make(permits) -> spawn(fun() -> loop(permits) end). p(sem) -> receive? -> v(sem) -> Sem!release. 31
Modelling Semaphores A process has to simulate a semaphore -module(semaphore). -export([make/1, p/1, v/1]). make(permits) -> spawn(fun() -> loop(permits) end). p(sem) -> receive? -> v(sem) -> Sem!release. 32
Modelling Semaphores Force a response from the semaphore modelling process %%possible continuation p(sem) -> Sem!{acquire, self()}, receive acquired -> ok end. 33
Modelling Semaphores In fact we can do it better Using the RPC request pattern %% a better continuation p(sem) -> Ref = make_ref(), Sem!{acquire, self(), Ref}, receive {acquired, Ref} -> ok end. 34
Semaphore Process Loop The semaphore modelling process as a finite state machine acquire release acquire Permits>0 receive acquire -> release -> Permits==0 receive release -> release 35
Semaphore Process FSM Implementing FSM One function per state loop(0) -> loop_release(); loop(permits) -> loop_both(permits). loop_release() -> receive release -> loop_both(1) end. %%continues 36
Semaphore Process FSM %%continuation loop_both(permits) -> receive {acquire, Pid, Ref} -> Pid!{acquired, Ref}, if (Permits-1)==0 -> loop_release(); true -> loop_both(permits-1) end; release -> loop_both(permits+1) end. 37
Using Semaphores A silly little example -module(semuser). -export([run/0]). -import(semaphore,[p/1,v/1]). run() -> Mutex = semaphore:make(1), spawn(fun () -> loop(["hi, Ho ], Mutex) end), spawn(fun () -> loop([ Chee, Tah ], Mutex) end). %%continues 38
Using Semaphores %%continuation loop(msg, Mutex) -> timer:sleep(500), p(mutex), io:format( My string is ~p.~n, [Msg]), timer:sleep(500), v(mutex), loop(msg, Mutex). 39
Selective Receive Clauses can have guards Guards must be composed from terminating functions (BIFs) loop(permits) -> receive {acquire,pid,ref} when Permits>0 -> Pid!{acquired, Ref}, loop(permits-1); release -> loop(permits+1) end. 40
Semaphores, last words Before you get the wrong idea Implementing semaphores using Erlang, Java, is for comparison purposes only It is not a natural or good programming style in a language with high level concurrency primitives! Nevertheless, the semaphore implementation showed certain patterns Finite state machine Selective receive 41
Distributed Execution Start Erlang node erl -sname node Start another Erlang node erl -sname other Send messages between nodes It just works For example to send to a registered process {process, node@localhost}!message 42
Distributed Example Start the math_server on one node For example on node@t60 Run: math_server:start(). Now, on another node we can off-load heavy factorial computation For example on other@t60 Run: math_server:compute_factorial( {math_server,node@t60}, 42). 43
Conclusions Erlang Functional Concurrent Distributed Basics 44