Komponere funksjoner i 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.

Kategorier: Polyglot.
RSS feed for kommentarene. Tilbaketråkk.

11 kommentarer til “Komponere funksjoner i F#”

  1. Bjarte Says:

    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 :)

  2. Torbjørn Says:

    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 :)

  3. EinarWH Says:

    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.

  4. Tormod Fjeldskår Says:

    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 :-)

  5. Ameth Says:

    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å)

  6. Torbjørn Says:

    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!

  7. Ameth Says:

    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.

  8. Stian Says:

    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.

  9. Torbjørn Says:

    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.

  10. Lispy C# (og hva er en closure) Says:

    [...] 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. [...]

  11. Curry-oppskrift for sultne utviklere Says:

    [...] 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. [...]

Skriv en kommentar

Tillatte tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


Einar W. Høst: Det er jo læringen som gjør det morsomt! Se også http://norvig.com/21-days...

Pagliacci: OBS! tl;wr. Det er vel akuratt det jeg sliter med med min læring innenfor pr...

Torbjørn: La oss anta to ulike definisjoner av Template Method pattern - de to ytterpunkte...

Lars-Petter: Hei igjen. Siden du inviterer til å ta diskusjonen i bloggen, og har tatt deg t...

Torbjørn: Lars-Petter: Det er én måte å se det på. Det er absolutt fortsatt Template M...

Lars-Petter: Hei. Har du ikke i prinsippet her gått over fra Template Method (arv) til Strat...

Christian Abildsø: I alle fall i C#, så føles dette som kode som blir mer fleksibel men vanskelig...

Torbjørn: Hei Henrik, og takk for tilbudet. Ble oppmerksom på Rasberry Pi for under en uk...

Henrik Sandaker Palm: Ang. større hobby prosjekt. Du er som er en slik rakker på programmering har j...

Øivind Nilsen: Slutt å bruke mobilnummeret mitt som eksempel !...

Mulig relaterte linker

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • En historie om programmering
  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Kategorier

  • .net ninja (37)
  • Bøker (17)
  • Diverse prosjekter (35)
  • DSL (10)
  • Erlang (10)
  • F# (5)
  • Hardware (1)
  • Jobb (77)
  • Julekalender (51)
  • kjempekjekt.com (23)
  • LISP/Clojure (33)
  • NNUG / community (60)
  • O/RM & databaser (10)
  • Off topic (116)
  • OO-design/clean code (30)
  • Podcasts (14)
  • Polyglot (77)
  • Ruby (27)
  • Silverlight / RIA (3)
  • Software/verktøy (20)
  • Softwareutvikling (21)
  • Testing / TDD (30)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (32)
  • WPF (9)
  • WTF (12)
  • Last ned Wallpaper

    Programmeringsbloggens tøffe skrivebordsbakgrunn med snippets fra ulike språk laster du ned her!

    Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Meta