Komponere funksjoner i F#


onsdag 2. juni 2010 F#

Jeg sitter og leser Real-World Functional Programming for tiden, og leker meg litt med F#. Boken går (alt for) sakte fremover, men rundt side 160 fikk jeg endelig en a-ha opplevelse. Det dreide seg om noe som kalles partial function application, også kalt currying. Kort fortalt betyr det å ta en funksjon som tar to eller flere parametre, for så å kalle den med noen av parametrene spesifisert – men ikke alle. Man får da tilbake en ny funksjon man kan kalle med resten av parametrene.

Denne teknikken har noen interessante bruksområder, sikkert mange flere enn dem jeg har sett til nå. Det fikk virkelig hjernen min til å begynne å jobbe, og drømmen jeg hadde i natt var følgelig veldig spesiell. En av ideene jeg fikk var at jeg ganske enkelt kan kombinere flere funksjoner, for å oppnå omtrent det samme som composable specification pattern i objektorientert programmering.

La oss for eksempel si at vi har endel regler for validering av et tall. Vi kan implementere disse reglene som forskjellige funksjoner:

// some validation rules
 
let larger_than_ten      n = n > 10
let smaller_than_hundred n = n < 100
let dividable_by_ten     n = n % 10 = 0    
let keep_away_from_fifty n = n <> 50

Disse fire funksjonene tar altså inn en integer n og returnerer en boolean som sier om n tilfredstiller regelen. F# har sterk, statisk typing, men det er sjelden vi må spesifisere typen selv, kompilatoren er nemlig ganske smart, og finner ut av dette på egen hånd.

Nå ønsker jeg å komponere disse reglene sammen til én valideringsfunksjon. Jeg kan da definere en operator for å kombinere funksjoner, og det er her magien ligger. Input til operatoren (funksjonen '+') er to generiske funskjoner og en verdi. Signaturen for operatoren, om jeg lagde den i C#, ville vært noe sånt som bool ComposeOperator(Func<T,bool> f, Func<T,bool> g, T x). Nok en gang slipper jeg å fortelle kompilatoren dette.

Deretter kan jeg opprette funksjonen validate ved å kombinere de fire reglene med min nye + operator:

// compose complex validation rules
 
let (+) f g x = f(x) && g(x)
 
let validate = // the complete validator function 
    larger_than_ten 
    + smaller_than_hundred 
    + dividable_by_ten
    + keep_away_from_fifty

Did that blow your mind? Dette føles ganske så genialt, om jeg må få si det selv. Nå gjenstår det bare å vise hvordan jeg kan bruke valideringsfunksjonen, og da kan jeg bruke partial application igjen i F#'s støtte for pipelining.

Først definerer jeg en liten funksjon for å konvertere en boolsk verdi til en streng ("valid" / "NOT valid"). Deretter lager jeg funksjonen check som først validerer tallet n, for deretter å sende resultatet til valid_as_string, som igjen sender resultatet til en print-funksjon. Pipelining minner om method chaining i objektorientert kode.

Legg spesielt merke til funksjonen printfn. Her har jeg spesifisert to parametre; et format-pattern og tallet n. Verdien som sendes gjennom pipelinen (som nå er streng-representasjonen av valideringsresultatet) legges til som en tredje parameter.

// use validation rule and report
 
let valid_as_string v =
    if v then "valid" else "NOT valid"
 
let check n = // illustrates pipelining (and partial application)
    validate n                 // validate
    |> valid_as_string         // convert result to string
    |> printfn "%d is %s" n    // print result
 
let some_numbers = [ 1; 10; 13; 20; 40; 50; 60; 90; 99; 100; 200 ]
for n in some_numbers do 
    check n

Til slutt lager jeg en liste med tall som jeg itererer over og validerer. Her er output:

1 is NOT valid
10 is NOT valid
13 is NOT valid
20 is valid
40 is valid
50 is NOT valid
60 is valid
90 is valid
99 is NOT valid
100 is NOT valid
200 is NOT valid

So there you have it, min første blogpost om F#. Jeg har såvidt begynt å lære meg språket, og selv om det er en god del som er mer eller mindre direkte overførbart fra Erlang (som touples, lister og pattern matching), så er det mye nytt også, og jeg gleder meg til å blogge mer om funksjonell programmering på .NET-rammeverket.


comments powered by Disqus