420 likes | 582 Vues
Erlang. Functional programming for real-world applications. functions lists recursion. “ yeah whatever ” runtime datatype. imperative compute by modifying state x = x + 2; overwrite memory “x” functional transform f( x ) x + 2 produces new value. Functions .
E N D
Erlang Functional programming for real-world applications
functions • lists • recursion • “yeah whatever” • runtime datatype
imperative • compute by modifying state • x = x + 2; • overwrite memory “x” • functional • transform • f(x) x + 2 • produces new value
Functions • are “first class citizens” • can return functions • can take functions as arguments • can be assigned to variables Necessary precondition, NOT sufficient! * javascript, glish (brrrrr!), python, ...
One function call, multiple bodies • look for name + number of arguments • notation: “functionname/2” • not found => EXPLODE! • patternmatch each clause (“body”) • seems to match? • [arguments satisfy contstraints?] • do evaluate that clause! • no more clauses? • EXPLODE! • sometimes you don’t care • _ is the wildcard; matches anything
Functionbehaviour = mathematical A function or expression is said to have a side effect if, in addition to producing a value, it alsomodifies some stateorhas anobservable interaction with calling functions or the outside world. int counter = 0; int plustwo( int x ) { counter++; return x+2; } • result depends on arguments alone • same arguments => same result • user input? file I/O? • every statement always yields something • maybe wrong but predictable • less side-effects = more pure • haskell: very pure • I/O special treatment • Erlang: pragmatically pure • I/O as you’d expect
Lists Mylist = [1,2,3]. Yourlist = [ ]. Things = [ “me”, 1, aap ]. Newlist = [ -1 | Things ]. = [ -1, “me”, 1, aap ].
map(Func, List) ListOfResults • transform a list of things into a list of other things • ingredients: • 1 List of things • 1 Function: F(x) y • produces: [F(x0), F(x1), ... ], x List • in fact ... identical to list comprehension • [ F(X) || X List]
fold(Func, Init, List) Result • a.k.a “reduce”; reduce a list of things to one value • ingredients: • 1 List of things • 1 Initial value • 1 Function F(x, y) z • yields: F(Init, F(x0, F(x1, ..))), x List (leftfold) • or: F(F(F(..., xn-1), xn), Init), x List (rightfold)
F(x,y) x + y, Init = 0, List=[1,2,3] Init [x0, x1, x2] F(Init, x0) F0 F0 [x1, x2] F(F0, x1) F1 F1 [x2] F(F1, x2) F2 0 [1, 2, 3] F(0, 1) (0+1) F0 [2, 3] F(F0, 2) ((0+1) + 2) F1 [3] F(F1, 3) ((0+1)+2)+3)
F(x,y) [x | y], Init = [], List=[1,2,3] [1, 2, 3] [] F(3, []) [3 | [ ] ] [1, 2] F0 F(2, F0) [ 2 | [ 3 | [ ] ] ] [1] F1 F(1, F1) [ 1 | [ 2 | [ 3 | [ ] ] ] ] [x0, x1, x2] Init F(x2, Init) F0 [x0, x1] F0 F(x1, F0) F1 [x0] F1 F(x0, F1) F2 A list can be recursively described as: “a list is an element followed by another list” [ Element | List ] [ Head | Tail ]
(tail)recursion sum( [ ] ) 0; sum( [Head|Tail] ) Head + sum(Tail). sum( List ) sum(List, 0). sum( [ ], Acc ) Acc; sum( [Head|Tail], Acc ) sum(Tail, Head+Acc). • typical ingredients: • 1 List of things • 1 recursive function • sometimes secret ingredients: • helper function having extra argument (accumulator) • never runs out of stackspace • example: • summing the values of all elements in a list
Variables aren’t! 1. x = 42 2. y = x2 3. x = 3 4. y = x2 ?!? • mathematical sense • single “assignment” • ‘=‘ matches, not assign • placeholders, not memorylocations • bind to expression • exclusively functionscope • 60% (or more) bug reduction! • most variables are transient, you don’t really need them • no global state • even more bug reduction • lockfree multithreading
WTF?! No loops? • recursion is the new for/while/do • stackspace?! • tail recursion + side-effect free = stackframe reuse
Datatypes or rather: terms • 0-9eE+-. (numbers) • [<term>, ... ] (list) • {<term>, ... } (tuple) • fun( ... )<expression>end. (functions) • “:alphanum:“ (strings) • lowercase or ‘:alphanum:’ (atom) • Uppercase (‘variable’) • <<V:n>> (n bits representing V)
Common idioms • somefunction(...) {ok,<succesreturnvalue>} error • otherfunction( X, Y ) case somefunction(X+Y) of error ‘oh noes!’; {ok, Value} 42 + Value end. • {ok, Result} = somefunction( ... ), otherfunction(Result)
A pragmatic excercise Shopping list 3 oranges 2.6 l milk 2 bananas 250 cookies Unit price uniboard 20000 apples 0.26 bananas 3.14 oranges 0.65 milk 1.15 cookies 0.06 Total amount = ?
... typically you do this: • * a struct/class with members • type: enum {orange, apple, banana, ...} • maybe ‘char*’ or std::string • price/amount: float? double? (template?!) • for shopping list, unit price list: • use array []? vector? list? • need initializing • array/vector - how did it work again? • struct itemdesc shoplist[] = { (banana, 2.0), ... }; • vector<itemdesc> vd( shoplist, (sizeof(shoplist)/sizeof(shoplist[0]))+1); • etc. • iterate over the vector/ for(i=0; i<num_items; i++) ... • find unit price in pricelist (need to write function) • multiply by amount, add to sum • oh yeah, do not forget • double sum = 0.0;
what you want is this: For each item in the shoppinglist multiply the amount by the unit price and sum the results. %% Both the shopping + pricelist are lists %% of 2-element tuples: an atom + a number ShoppingList = [ {3, oranges}, {2.5, milk}, ...]. PriceList = [ {uniboard, 20000}, {apples, 0.26}, .... ].
Erlang specific • pure enough, yet pragmatic • sockets/file/user I/O simple • database connectivity (MySQL) • pattern matching on binary data • protocol analysis • decoding data • working with binary data per se • arbitrary N-bit values • splitting/joining chunks of binary data • conversion to/from other datatypes • distributed fault-tolerant systems
Binary pattern matching %% clause 1: binary block which %% is at least 2*32bits long AND %% first 32bits read 0xABADDEED %% clause 2: binary block starting with 0xFFFFFFFF decode( << 16#abaddeed:32, SeqNo:32/unsigned-little-integer, _/binary >> ) io:format(“Mark5B DiskFrameHeader, SeqNr ~w~n”, [SeqNo]); decode( << 16#ffffffff:32, _/binary >> ) io:format(“MarkIV tapeframeheader syncword~n”). %% 14 bit color-space with 5 bits Red, 5 bits Green and 4 bits Blue %% match-and-extract in one operation << Red:5, Green:5, Blue: 4, _/bitstring >> ) = Blob, %% The symbols “Red” “Green” and “Blue” are usable as numbers here! io:format(“RGB[~p, ~p, ~p]~n”, [Red, Green, Blue]).
Processes • very lightweight “threads” of execution • lockfree • may map onto O/S threads • extremely cheap to create • come for free • no side-effects • no global/shared state • interact with outside world/each other • only via message passing • e.g. sockets send messages to their owner • tell apart by Process-ID • so fundamental it’s a built-in type • Erlang knows where a process is running
Distributed Erlang system • processes run inside Erlang nodes • nodes run on hosts • nodes are identified by their nodename • 1 host may run multiple nodes • nodename unique ONLY per host: nodename@host • the set of connectednodes is the system • “registered processes” • bind a name to a process on a node • send messages to that name • you don’t have to know where the process is; Erlang does! • unbelievably trivial to use/build!
In graphical form HostA HostB node1 node1 node2 node2 Supervisor process Worker process
Fault tolerant • processes can be linked • crash? ‘EXIT’ signal propagates along links • ‘EXIT’ signal is just a message • allows one process to monitor one or more other processes A B C D
4 frontnode 4 backnode >20 Gbps/link 16x 10Gbps 16x 10Gbps 1Gbit/s ethernet UniBoard
Polyphase Filterbank Nios2 CPU registers registers registers Delay Module registers 1Gbit PHY FPGA VHDL Memorymapped I/O via UDP/IP Hardware Software
FPGA client library one controller one fpga • Registermap: • type of the register (details follow) • name of the register (symbolic) • address of the register in the memory map • How to communicate with the FPGA: • UDP/IP • needs IP-address • emulator (for testing/playing)
FPGA client library Available registerdefinition types for the controller fpga:bit( <name>, <bit #>, <address> ) fpga:bitrange( <name>, <startbit>, <nbit>, <address> ) fpga:word( <name>, <address> ) fpga:range( <name>, <# words>, <address> )
FPGA client library Available register commands fpga:read( <name> ) fpga:write( <name>, <value> ) fpga:or_( <name>, <value> ) fpga:and_( <name>, <value> ) fpga:xor_( <name>, <value> )
FPGA client library FLASH-memory commands fpga:config_read( <address> ) fpga:config_erase( <address> ) fpga:config_write( <address>, <<256bytes>> )
FPGA client library Actually executing FPGA commands Controller = fpga:fpga(Personality, Protocol, [Options]), fpga:execute( Controller, [ fpga:write(pps_enable, 0), fpga:write(config_n_sta, 8), fpga:write(pps_enable, 1) ] ).
Example: firfilter • 3 2 1 0 • ADDRESS 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ • 0x00000024 | PN N N | • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ • P = PPS_ENABLE (1bit)NNN = NUM_TAP (3bit) • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ • 0x0000abcc | CONTROL/STATUS REGISTER | • +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
File: firfilter.erl -module(firfilter). %% this module is called firfilter, driving one of those -import(fpga). %% enables us to use functions from the client library -behaviour(fpga.personality). %% this module implements an FPGA personality %% Return the list of registers defined for this personality. %% Called automatically by the FPGA control framework registers() -> {ok, [ %% a 3 bit field starting at bit 5 in the word at 0x24 fpga:bitrange(num_tap, 5, 3, 16#24), %% another bit in that same register/word fpga:bit(pps_enable, 28, 16#24), %% one word (32bits) at location 0xabcc fpga:word(control_status, 16#abcc), ] }. %% define a high-level command for this personality start_filter(FPGA, NumTap) -> %% disable the PPS fpga:execute(FPGA, fpga:write(pps_enable, 0)), %% read the control status register case fpga:execute(FPGA, fpga:read(control_status)) of %% if the control_status register is “1” (zero) %% we must first XOR it with “42” ... 1 -> fpga:execute(FPGA, fpga:xor(control_status, 42)); %% otherwise do nothing _ -> ok end, %% now write the number of taps into the register for that and immediately %% after that start the PPS again. So far we only used single commands, %% however, you can easily execute a number of commands in one go: fpga:execute(FPGA, [fpga:write(num_tap, NumTap), fpga:write(pps_enable, 1)]). -module(firfilter). %% this module is called firfilter, driving one of those -import(fpga). %% enables us to use functions from the client library -behaviour(fpga.personality). %% this module implements an FPGA personality %% Return the list of registers defined for this personality. %% Called automatically by the FPGA control framework registers() -> {ok, [ %% a 3 bit field starting at bit 5 in the word at 0x24 fpga:bitrange(num_tap, 5, 3, 16#24), %% another bit in that same register/word fpga:bit(pps_enable, 28, 16#24), %% one word (32bits) at location 0xabcc fpga:word(control_status, 16#abcc), ] }. %% define a high-level command for this personality start_filter(FPGA, NumTap) -> %% disable the PPS fpga:execute(FPGA, fpga:write(pps_enable, 0)), %% read the control status register case fpga:execute(FPGA, fpga:read(control_status)) of %% if the control_status register is “1” (zero) %% we must first XOR it with “42” ... 1 -> fpga:execute(FPGA, fpga:xor(control_status, 42)); %% otherwise do nothing _ -> ok end, %% now write the number of taps into the register for that and immediately %% after that start the PPS again. So far we only used single commands, %% however, you can easily execute a number of commands in one go: fpga:execute(FPGA, [fpga:write(num_tap, NumTap), fpga:write(pps_enable, 1)]).
int main() { clock_t nof_ticks_report; TUInt32 aTest[4] = {0, 0, 0, 0}; // prepare for entering mainloop nof_ticks_report = clock(); while( 1 ) { clock_t now = clock(); // Task: Ethernet traffic if( (ptPacket=NET_Receive())!=NULL ) handlePacket(ptPacket); // Report if( (now-nof_ticks_report)>=2000 ) { printf("aTest[0x%08lx] %08lx %08lx\n", &aTest[0], aTest[0], aTest[1]); nof_ticks_report = now; } } }
Ctrl = uniboard:start(“10.99.0.0”). • uniboard:execute(Ctrl, fn0, fpga:read(pps_enable)). 1 • uniboard:execute(Ctrl, [1,6], fpga:read(pps_enable)). [{fn1, 0} , {bn2, 0}] • uniboard:read_sensors(Ctrl, all, fpga_temp). [{fn0, 28}, {fn1, 30}, {fn2, timeout}, .... {bn3, 32}] • uniboard:execute(Ctrl, fn, fpga:read(pps_enable)). [{fn0, 1}, {fn1, 0}, {fn2, timeout}, {fn3, 1}] • uniboard:execute(Ctrl, [fn1, bn2], fpga:write(pps_enable, 1)). [{fn1, ok}, {bn2, ok}] • uniboard:execute(Ctrl, [{fn,1}, {bn,1}], fpga:read(pps_enable)). [{fn1, 1}, {bn1, 0}]
File: uniboard/i2c.erl read_sensors(FPGA, Sensors) -> %% From the sensorslist, build the contents of the I2C %% protocol region (the commands to read them) I2CProto = mk_i2cproto(Sensors), %% We must write that to the i2c-command region of the memory-mapped %% i2c master on the fpga. then we must write the activate bit %% in the command register. Actually, we must make that bit go %% through an 0 -> 1 transition, which we will force by successively %% writing a 0 and then a 1. Force all actions to return "ok". %% (or rather, explode if they don't) I2CResult = case fpga:execute(FPGA, [fpga:write(i2c_proto, I2CProto), fpga:write(i2c_activate, 0), fpga:write(i2c_activate, 1)]) of [ok, ok, ok] -> %% Having done that, now, in theory, %% we should wait for the i2c interrupt %% to appear. Let's wait 1/5th of a second ... timer:sleep(200), %% Now read the full result region and extract the %% values the i2c cruft has given us fpga:execute(FPGA, fpga:read_binary(i2c_result)); [timeout,timeout,timeout] -> %% Bollox. A timeout. Let's pass this on timeout %% No other cases - the code should explode in this case %% since something happened that we don't expect. %% The FPGA commands should either succeed or timeout. end, decode_i2c_result(I2CResult, Sensors). decode_i2c_result([Word|MoreWords], [_Sensor|MoreSensors], Acc) -> %% The i2c result is returned as 1 byte of value and %% one byte status. %% Status==0 => read succeeded, %% Status==1 => read failed {Value, Remain} = case split_binary(Word, 2) of {<<_:8, 1:8>>, R1} -> {fail, R1}; {<<V:8/unsigned-little, 0:8>>, R2} -> {V, R2} end, decode_i2c_result([Remain|MoreWords], MoreSensors, [Value|Acc]).
UniBoard commandpacket Packetformat Generic instruction format Wait-for-1PPS instruction prefix Write config data [program FLASH image]
Available commands Read N 32bit words starting from START ADDRESS (Over)Write N 32bit words from the packet to START ADDRESS Read/Modify/Write N 32bit words from START ADDRESS + packet to START ADDRESS