Komponere funksjoner i F#
- Wednesday, June 2nd, 2010
- Skriv en kommentar
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.
Kategorier: Polyglot.
RSS feed for kommentarene.
Tilbaketråkk.



June 3rd, 2010 at 7:41 am
Elegant løsning. Sitter forøvrig og leser akkurat samme boken selv. Enig med at det tar tid før boken blir spennende. Alle sammenligningene med C# kunne de klart seg uten etter min mening. Til tross for det, er det en ok bok :)
June 3rd, 2010 at 7:57 am
Sammenligningene med C# er unødvendige for dem som allerede behersker bruk av funksjonell kode som Linq, og om jeg hadde lest boken for to år siden så hadde jeg nok satt pris på det, men nå føles det litt overflødig ja.
Det som derimot irriterer meg mest er mengden med “meta-tekst”. Hvert lille delkapittel (på rundt to sider) begynner med å fortelle hva delkapittelet skal handle om. Deretter forteller man det man skal fortelle, og at vi også så litt av dette i kapitel X, for så å fortelle hva man kunne ha fortalt, men som er spart til kapittel Y. Til slutt har man et avsnitt om hva neste delkapittel skal handle om, som blir gjentatt i starten av neste delkapittel, som jo er det påfølgende avsnittet. Boken kunne dermed vært kokt ned til en tredjedel. :-0
Men grundighet er også bra, om man har tid til det, så jeg skal holde ut :)
June 3rd, 2010 at 9:57 am
Den boken har vært en ‘anticepointment’ for meg også, synes den er veldig treig og pratete. Men jeg får prøve å komme meg til side 160 jeg også! Underlig, ettersom Tomas P er en flink fyr, og Jon S har vært involvert, formodentlig for å hjelpe til med teksten. Og C# in Depth har ikke de samme problemene. Tvert imot, den er grundig, men samtidig engasjerende og interessant.
June 4th, 2010 at 3:18 pm
Veldig elegant måte å spesifisere valideringsregler på. Ingen vil ha vanskeligheter med å forstå hvilke regler som ligger til grunn for ‘validate’-funksjonen når de ser implementasjonen. Jeg leser gjerne fler F#-poster forresten :-)
June 7th, 2010 at 3:00 pm
Fjern alle “let”-ene, bruk (den allerede definerte) “.” i stedet for “+” for funksjonskomposisjon, s/n % 10 = 0 /n `mod` 10 == 0/, s/s/printfn/printf/ s@@/=@, s/;/,/, legg til “(|>) = flip ($)” og bytt ut de 2 siste linjene med “mapM_ (putStrLn . check) some_numbers”, så er koden din gyldig Haskell. :P
Det er forresten et verktøy i Hackage for å gjøre om funksjoner til deres curriede ekvivalenter: http://hackage.haskell.org/package/pointfree
% pointfree ‘valid_as_string v = if v then “valid” else “NOT valid”‘
valid_as_string = flip (flip if’ “valid”) “NOT valid”
% pointfree ‘y (\f n -> if n == 0 then 1 else n * f (n-1))’
y (ap (flip if’ 1 . (0 ==)) . ap (*) . (. subtract 1))
(Og beklager hvis jeg begynner å bli litt vel evangelist-aktig nå)
June 7th, 2010 at 3:13 pm
Ja, nå synes jeg du tar litt av her Ameth, men det gjør ingen ting ;) Orginalt å komme med sed-kommandoer i stedet for å bare vise hvordan koden ser ut!
June 7th, 2010 at 3:45 pm
For å understreke at det kun var mindre syntaktiske forskjeller, men det visste du vel kanskje. F# har til og med klart å stjele monader fra Haskell (“Computational Expressions” kaller de det), så det virker som et greit nok språk.
June 9th, 2010 at 11:18 am
Utrolig stilig definisjon av + operator. Det ser ut som et matteuttrykk. Skjønner virkelig kompilatoren at f og g er funksjoner ut fra uttrykket på høyre side? Angrer på at jeg ikke konsentrerte mer på funksjonell programmering da jeg gikk på universitetet.
June 9th, 2010 at 1:28 pm
Jepp, F# skjønner det fint. Om jeg hiver definisjonen av (+) inn i det interaktive F#-vinduet gir det meg tilbake en beskrivelse av typen:
val ( + ) : ('a -> bool) -> ('a -> bool) -> 'a -> bool
‘a er F#’s måte å si at dette er en generisk type – ‘a kan være hva som helst, sålenge det er det samme overalt. (‘a -> bool) er en funksjon som tar inn en ‘a og returnerer en boolean. Den totale funskjonen tar inn to slike funksjoner og en ‘a-verdi, og returnerer en boolean.
F# skjønner at f og g må våre funksjoner fordi jeg bruker dem som funskjoner – jeg kaller dem med x som parameter. Og siden jeg og’er dem sammen (&&) skjønner kompilatoren at de må returnere boolean.
August 9th, 2010 at 9:52 am
[...] Spesielt interesserte kan også merke seg funksjonen comp på siste linje. Dette er en funksjon som komponerer to eller flere funksjoner, og tilsvarer på en elegant måte hvordan jeg i C# lagde en ny lambda som kombinerte to andre lambdaer. Se gjerne Komponere funksjoner i F#, som også omhandler komposisjon av funksjoner i funskjonelle språk. [...]
August 11th, 2010 at 2:34 pm
[...] Navnet stammer fra logikeren Haskell Curry, som gjenoppdaget denne teknikken etter at den orginalt var beskrevet av Moses Schönfinkel. Det alternative navnet “Schönfinkelisation” for teknikken har blitt foreslått!!! Partial application er et mere beskrivende navn, og får ikke magen til å rumle på samme måte. Jeg har tidligere demonstrert partial application i F# her. [...]