Curry-oppskrift for sultne utviklere


onsdag 11. august 2010 Clojure F# C# JavaScript Funksjonell programmering

CurryChef1. 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..

Curry i F#

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.

Curry i Clojure

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.

1 ; First we define a simple add function
2 ; taking two arguments, a and b.
3 (defn add [a b]
4       (+ a b))
5
6 (println (add 1 2)) ; -> 3
7
8 ; Then we create a curried version
9 (def add-five
10      (partial add 5))
11
12 ; and use it in a couple of examples
13 (println (add-five 1)) ; -> 6
14 (println (add-five (add-five 0))) ; -> 10

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?

Curry i JavaScript

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:

1 Function.prototype.curry = function () {
2     var slice = Array.prototype.slice,
3         args = slice.apply(arguments),
4         that = this;
5     return function () {
6       return that.apply(null, args.concat(slice.apply(arguments)));
7     };
8 };

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.

12 var add = function (a, b) {
13   return a + b;
14 };
15
16 var add5 = add.curry(5);
17 document.writeln(add5(1)); // prints 6
18
19 // what's really going on??
20
21 var add5_explained = function (b) {
22   return add.apply(null, [5].concat([b]));
23 };
24
25 document.writeln(add5_explained(1)); // prints 6

Curry i C#

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.

Oppsummering

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.


comments powered by Disqus