Den lille Erlanger
1 kommentar
Dette er en tutorial som lærer deg grunnleggende Erlang og noen grunnleggende teknikker fra funksjonell programmering. Formatet er inspirert av boken The Little Schemer (og de andre bøkene i den serien). Vi begynner helt fra scratch, men bygger raskt opp ganske mye kunnskap om språket. Følg nøye med!
Har du ikke peiling på hva Erlang er for noe kan du leste min lille introduksjon som jeg skrev i 2010.
La oss begynne…
|
Er du klar for å lære litt Erlang og funksjonell programmering? |
Ja! |
|
|
|
|
Hva er 1? |
Det er et heltall. |
|
|
|
|
Hva er X? X = 2. |
X er en variabel. X er bundet til verdien 2. Legg merke til punktumet etter uttrykket. Alle uttrykk avsluttes med et punktum – som om de er normale setninger. |
|
|
|
|
Hvilken verdi er Y bundet til? X = 3, Y = X. |
X er bundet til verdien 3, og Y er bundet til X. Y er derfor også bundet til verdien 3. Legg merke til at dette uttrykket består av to del-uttrykk, og at de er separert med komma – akkurat som leddsetninger. |
|
|
|
|
Følgende uttrykk vil feile. Hvorfor? X = 4, Y = 5, Y = X. |
En variabel i Erlang kan kun bindes til en verdi én gang. Etter at vi har sagt at Y er bundet til 5 kan den ikke bindes på nytt. Hvis X er lik 4 og Y er lik 5, så kan X ikke være lik Y. |
|
|
|
|
Følgende uttrykk feiler ikke. Hvorfor? X = 6, Y = 6, Y = X. |
Både X og Y er bundet til verdien 6. X er derfor lik Y, så X = Y feiler ikke. |
|
|
|
|
Hva er X bundet til nå? X = foo. |
X er bundet til atomet foo. Et atom er en grunnleggende datatype på lik linje med tall. De skiller seg fra variabler ved å begynne med en liten bokstav – variabelnavn begynner alltid med en stor bokstav. |
|
|
|
|
Hva er dette? {epler, bananer, plummer}
|
En datastruktur som kalles en tuple. I dette tilfellet er det en 3-tuple (også kalt triplet) av tre atomer. |
|
|
|
|
Hva er verdien av Person? GivenName = "Bill",
SurName = "Gates",
Person = {person, GivenName, SurName}.
|
Person er bundet til en triplet bestående av atomet ‘person’ og to strenger. Verdien er {person, "Bill", "Gates"}. |
|
|
|
|
Hva er verdien av G og S? P = {person, "Steve", "Jobs"},
{person, G, S} = P.
|
G er bundet til strengen “Steve” og S er bundet til strengen “Jobs”. Dette kalles pattern matching. |
|
|
|
|
Vil dette fungere? P = {author, "Joe", "Armstrong"},
{person, G, S} = P.
|
Nei, atomet person matcher ikke atomet author. |
|
|
|
|
Hva er verdien av dette uttrykket? P = {man, "Adam"},
{_, Name} = P,
Name.
|
Verdien av dette uttrykket er strengen “Adam”. Og deluttrykk 2 fungerer fordi vi nå matcher et atom med en underscore, som kan matche hva som helst. |
|
|
|
|
Hva er dette? [1, 2, 3, 4, 5]. |
Dette er en liste. Listen består av fem tall. |
|
|
|
|
Hva er verdien av X? [_, X, _] = [1, 2, 3]. |
X er bundet til heltallet 2. Dette er også et eksempel på pattern matching. |
|
|
|
|
Hva er verdien av dette uttrykket? L = [2, 4, 8, 16, 32], [Head|Tail] = L, Head. |
Vertikal strek (pipe) brukes til å splitte en liste mellom “hodet” og “halen”. Dette uttrykket evaluerer til heltallet 2. |
|
|
|
|
Og dette? L = [2, 4, 8, 16, 32], [Head|Tail] = L, Tail. |
Verdien av dette uttrykket er listen [4, 8, 16, 32]. |
|
|
|
|
Hva er hodet og halen av en liste med ett element? [Head|Tail] = [a]. |
Head = a og Tail = [], den tomme listen. |
|
|
|
|
Kan du plukke ut hode og hale av den tomme listen? [Head|Tail] = []. |
Nei, dette vil feile. |
|
|
|
|
Hvilken verdi er det vi har trukket ut fra listen nå? L = [2, 4, 8, 16, 32], [_|T] = L, [_|T2] = T, [H|_] = T2, H. |
T er halen av den opprinnelige listen, dvs. en liste som begynner med 4. T2 er halen av denne listen igjen, og begynner på 8. Variabelen H er bundet til hodet på T2, og uttrykket returnerer derfor 8. |
|
|
|
|
Kan det forrige uttrykket skrives mere kompakt? L = [2, 4, 8, 16, 32], [_, _, H | _] = L, H. |
Tydeligvis, for dette uttrykket returnerer også 8. |
|
|
|
|
Hva er verdien av double(1)? double(X) -> X + X. |
double har her blitt definert som en funksjon, og resultatet av å evaluere double(1) er heltallet 2. |
|
|
|
|
Hva er verdien av sum([1, 2, 3]) gitt følgende definisjon? sum([A, B, C]) -> A + B + C. |
Her har vi laget en sum-funksjon som har en formell parameter som må være en liste med tre element. Igjen bruker vi pattern matching, og svaret er 6. |
|
|
|
|
Hva er verdien av second([1, 2, 3, 4])? second([_, X | _]) -> X. |
2. |
|
|
|
|
Hva er verdien av S1 og S2? second([_,X|_]) -> X; second(_) -> no_second_element. S1 = second([1]), S2 = second([1, 2]). |
S1 er bundet til atomet no_second_element, mens S2 er bundet til tallet 2. Funksjonen second har nå to ulike klauser (clauses), og pattern matching avgjør hvilken av dem som blir brukt. |
|
|
|
|
Nå bruker vi det vi har lært til å telle antall elementer i en liste. count([ ]) -> 0; count([_,T]) -> 1 + count(T). |
Antall elementer i en tom liste er 0. Antall elementer i en liste som ikke er tom er 1 pluss antall elementer i halen av listen. Funksjonen kaller seg selv til den har “spist opp” hele listen. Dette er en rekursjon. |
|
|
|
|
Her er en annen måte vi kan telle elementene i en liste på. Hva er forskjellen? count(List) -> count(List, 0). count([], Acc) -> Acc; count([_|T], Acc) -> count(T, Acc + 1). |
Her har vi to ulike funksjoner med samme navn men med ulikt antall parametre. Vi kaller dem count/1 og count/2 fordi de har henholdsvis ett og to formelle parametre. count/2 bygger opp resultatet i variabelen Acc (står for accumulator).
Legg merke til at det rekursive kallet til count er det siste som skjer i count. Dette kalles en halerekursjon, og er mye mere effektivt enn rekursjonen i den forrige implementasjonen av count. Men det kan være mere komplisert å forstå koden.. |
|
|
|
|
Hva er resultatet av dette uttrykket? F = fun (X) -> X * 3 end, F(3). |
Variabelen F bindes til en anonym funksjon som multipliserer argumentet sitt med 3. Resultatet av uttrykket er 9. |
|
|
|
|
Funksjoner kan være argumenter til andre funksjoner. Hva er verdien av X her? map(_, []) -> [];
map(Fun, [Head|Tail]) ->
[Fun(Head) | map(Fun, Tail)].
X = map(fun (X) -> X div 2 end,
[2, 4, 8, 16]).
|
map sier at om det andre argumentet er en tom liste så er resultatet også en tom liste. Om listen ikke er tom er resultatet en ny liste som består av resultatet av å evaluere Fun av det første elementet pluss map av alle de andre elementene. map kaller seg selv rekursivt til den har spist opp hele listen.
X er listen [1, 2, 4, 8]. map er en høyereordens funksjon som utfører en gitt funksjon på elementene i en sekvens. Den er også kjent som apply-to-all, mapcar, select, apply_map og collect. |
|
|
|
|
Hva er X? filter(_, []) -> [];
filter(F, [H|T]) ->
case F(H) of
true -> [H | filter(F, T)];
false -> filter(F, T)
end.
IsEven = fun (X) -> X rem 2 == 0 end.
X = filter(IsEven, [1,2,3,4,5,6]).
|
X er listen [2, 4, 6]. |
|
|
|
|
La oss se på en versjon som bruker halerekursjon. Hva blir X nå? filter(F, L) -> filter(F, L, []).
filter(_, [], Acc) -> Acc;
filter(F, [H|T], Acc) ->
case F(H) of
true -> filter(F, T, [H|Acc]);
false -> filter(F, T, Acc)
end.
IsEven = fun (X) -> X rem 2 == 0 end,
X = filter(IsEven, [1,2,3,4,5,6]).
|
X er listen [6, 4, 2]. Listen er i revers i forhold til den første varianten på grunn av måten vi bygger opp Acc på. Det er mest effektivt å plukke elementer fra og legge elementer til hodet av en liste, og derfor blir det sånn. Men Erlang kan raskt reversere en liste, så dette er egentlig ikke noe problem. |
|
|
|
|
Vi har sett på et par veldig grunnleggende funksjoner i funksjonell programmering – map og filter. Det naturlige neste steget kalles fold (også kjent som reduce, aggregate, accumulate, compress og inject): fold(Acc, _, []) -> Acc; fold(Acc, F, [H|T]) -> fold(F(H, Acc), F, T). |
Hmm, ok?! |
|
|
|
|
Fold komprimerer en sekvens til én verdi. Hva er verdien av dette uttrykket? fold(0,
fun (X, Acc) -> X + Acc end,
[1, 2, 3, 4]).
|
Heltallet 10, som er summen av tallene i listen. |
|
|
|
|
Hva er et bedre navn på funksjonen foo? foo(F, List) ->
G = fun (X, Acc) ->
case F(X) of
true -> [X|Acc];
false -> Acc
end
end,
fold([], G, List).
|
Et bedre navn på foo ville vært filter. foo gjør akkurat samme jobben som filter-funksjonen du så tidligere, men denne gangen ved hjelp av fold. |
|
|
|
|
Hva er et bedre navn på funksjonen bar? bar(F, List) ->
G = fun (X, Acc) -> [F(X)|Acc] end,
fold([], G, List).
|
map. |
|
|
|
|
La oss kombinere filter, map og fold for å analysere et datasett. Hvilken verdi er X bundet til etter å ha evaluert følgende? is_man({man, _, _}) -> true;
is_man(_) -> false.
L = [{man, "Jan", 23},
{woman, "Kari", 25},
{man, "Rune", 30},
{man, "Vidar", 44},
{woman, "Cecilie", 35}],
L2 = filter(fun is_man/1, L),
L3 = map(fun ({_, _, Age}) -> Age end, L2),
X = fold(0, fun (X, Acc) -> X + Acc end, L3).
|
L er en liste med persondata. L2 er alle mennene. L3 er en liste med aldrene til mennene. Og X er summen av alderen til alle mennene – som er 97. |
|
|
|
|
En aller siste fold: Gitt listen L ovenfor, hva er verdien av dette uttrykket? fold({men, 0, women, 0},
fun (P, Acc) ->
{men, M, women, W} = Acc,
case P of
{man, _, Age} ->
{men, M + Age, women, W};
{woman, _, Age} ->
{men, M, women, W + Age}
end
end,
L).
|
4-tuplet {men, 97, women, 60}, |
|
|
|
|
Lærte du noe? |
Tja, jeg tror jeg begynner å se hvor elegant det er å jobbe med lister med funksjoner som map, filter og fold. Og kanskje jeg skal ta en nærmere titt på Erlang.., pattern matching virker spesielt kult :) |
|
|
|
Programmeringsbloggen
Programming Erlang: Software for a Concurrent World
Den andre boken jeg kunne ha valgt er O’REILLYs
Erlang er et HOT språk akkurat nå. Og der det er ny interesse dukker det alltid opp nye bøker. Første mars i år kom
På Manning Publications kan man også finne en Erlang-bok. Den er riktignok ikke ferdig enda, men det er nok bare finpuss igjen, og gjennom Manning Early Access Program (MEAP) kan man få digital tilgang til den allerede. Boken heter
Den siste Erlang-boken jeg har hørt om,
Den første oppgaven jeg gav
Erlang var et internt språk hos teleselskapet Ericsson fra 1986 til 1998. De brukte språket til å programmere hardware brukt i GPRS og 3G nettverk verden over. I 1998 bestemte de seg for at de ikke lenger ville kode i et proprietært språk, og åpnet derfor opp kildekoden. Problem solved!
Flere bedrifter i andre bransjer, som også har behov for å utvikle systemer med høy ytelse og kapasitet, og med minimal eller ingen nedetid, har i de siste årene tatt i bruk Erlang. Av de mest profilerte prosjektene kan jeg nevne
Katz bemerker derimot at det også er en rekke områder hvor han ikke ville benyttet Erlang. Han sier språket egner seg mindre bra til ting hvor man er mere avhengig av state, som i programmering av brukergrensesnitt, og han sa han slet noe voldsomt med å skrive enhetstester i Erlang (en erfaring jeg foreløpig ikke deler – enhetstester i Erlang er helt konge!). 