onsdag 11. august 2010 Clojure F# C# JavaScript Funksjonell programmering
1. Ta en funksjon som har to eller flere argumenter
2. Tilsett noen av argumentene, men la det være igjen litt til senere
3. Rør godt rundt
4. Ta vare på resultatet i en ny funksjon (dette er curryen av den orginale funkjonen)
5. Stek på 120 grader i 15 minutter
6. Du kan nå kalle curryen ved å tilsette de siste ingrediensene..
Currying, er (litt enkelt forklart) en teknikk for å omforme en funksjon som tar mange argumentere til en mer spesifik funksjon som tar færre argumenter. Dette er et vanlig begrep innen funksjonell prorammering. Currying kan bl.a. brukes til å på en enkel måte få mere beskrivende funksjonsnavn og samtidig redusere kodeduplisering. I mer avanserte tilfeller brukes det for eksempel til å tilpasse funksjoner for en "call chain"/pipeline, og gjør kode mer kompakt.
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.
Uten noe mer Om Og Men vil jeg nå demonstrere litt currying i ulike språk..
open System // Define a simple add function taking two arguments let add a b = a + b Console.WriteLine(add 1 2) // -> 3 // Then we apply the function partially, // sending in just a single argument let addFive = add 5 Console.WriteLine(addFive 1) // -> 6
Ser du hvordan jeg kaller add-funksjonen med bare ett argument, og dermed får returnert tilbake en ny funksjon? Currying er en naturlig del av funksjonelle språk som F#, og det skjer helt implisitt.
I Clojure kan man ikke "kalle funksjoner delvis" ved å bruke færre argumenter enn funksjonen skal ha, slik som jeg nettopp gjorde i F#. I stedet bruker man funksjonen partial. Den tar som input en funksjon og en rekke argumenter, og returnerer en ny funksjon hvor de gitte argumentene er predefinert.
Legg merke til at definisjonen av add-five i linje 9 bruker def, ikke defn. Defn brukes til å definere funksjoner – det er ikke det vi gjør her, i stedet kaller i partial, som returnerer en funksjon. Gav det mening?
JavaScript har i utgangspunktet ikke noen støtte for partial application. Men i et dynamisk, løst typet språk, med first class functions som er fleksible i forhold til antall funksjonsparametre, og med støtte for closures, er det ikke noe problem å implementere currying. Følgende er inspirert av JavaScript-guru Douglas Crockford:
Prinsippet er at vi lager en closure som husker argumentene som er spesifisert, og som legger dem til argumentene når metoden blir kalt neste gang. Det hele virker mer komplisert enn det er fordi arguments ikke er et orntlig array, så vi må gjøre litt triksing og miksing for å joine to av disse.
Nedenfor kan du se hvordan curry kan brukes på en funksjon som i utgangspunktet tar to argumenter. Jeg inkluderer også en add5_explained hvor jeg forsøker å vise hvordan det hele virker ved å gjøre curryingen manuelt.
C# er i utgangspunktet ikke et språk hvor vi forventer å finne currying, men med Linq ble det innført mange funksjonelle godbiter, og det er nå mulig å støtte dette om man vil. Her er et eksempel:
Func<int, int, int> add = (a, b) => a + b; Console.WriteLine(add(1,2)); // -> 3 Func<int, int> addFive = add.Curry()(5); Console.WriteLine(addFive(1)); // -> 6
Så hvor kommer Curry-metoden fra? Den er en extension method som ser slik ut:
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>( this Func<T1, T2, TResult> f) { return p1 => p2 => f(p1, p2); }
Ganske stilig må jeg innrømme! Denne koden er stjålet rått og brutalt fra en fyr som heter Paul Stancer. For et komplett sett med curry-extensions klikker du her.
Currying er et av de begrepene alpha-geeks har begynt å slenge rundt seg nå som funksjonelle språk har begynt å ta av også i den komersielle utvikler-sfæren. Det kan derfor være greit å vite hva det dreier seg om. Og som vanlig er det noe ganske enkelt og helt ufarlig som skjuler seg bak et litt merkelig navn.
Du har blitt servert et lite knippe med muligheter for å gjøre currying i ulike språk – noen hvor currying er en naturlig del av språket, og noen hvor man kan få det til på andre måter. Jeg har ikke sagt så mye om hvordan man best utnytter disse mulighetene – når det er lurt, og hva man oppnår – det får bli en fremtidig blogpost. Har du noe å bidra med om det, eller sitter på kunnskap om currying i et språk jeg ikke brukte, håper jeg du legger igjen en kommentar.
Håper maten smakte!
PS: For de som er matematisk anlagt, og som ønsker å være helt presise, skal det sies at currying og partial application ikke er nøyaktig det samme. Likevel bruker jeg dem her om hverandre, og er litt vag i definisjonen. Jeg har forsøkt å være praktisk i min tilnærming, og håper jeg ikke støter dem som har en doktorgrad eller to innen funksjonell programmering. Se gjerne Currying != Generalized Partial Application?! på Lambda the Ultimate.