Lispy C# (og hva er en closure)

I et forsøk på å gi et bedre innblikk i hva Lisp er for noe har jeg skrevet litt lisp-inspirert C#-kode. Jeg snakker ikke om Lisp-syntaks, men mer programmering i et Lisp-tankemønster. Koden illustrerer hvordan jeg tenker når jeg programmerer i Lisp (og til dels funksjonelle språk generelt). Eksperimentet er mest for gøy, men om det kan gi noen en a-ha-opplevelse hadde det også vært kjekt.

Programmet nedenfor kalkulerer og skriver ut power of 2 for tallene 1 til 10. For å gjøre dette har jeg definert endel lambda-uttrykk, og kombinerer dem for å oppnå ønsket resultat. 

static void Main(string[] args)
{
    Action<int, Action> Times = (n, action) =>
    {
        for (int i = 0; i < n; i++)
            action();
    };
 
    Action<IEnumerable<string>> Output = (s) =>
    {
        foreach (var item in s)
            Console.Write("{0} ", item);
        Console.WriteLine();
    };
 
    Func<int, Func<IEnumerable<string>>> PowerStringIterator = (powerbase) =>
    {
        int x = 0;
        return () =>
        {
            x++;
            return new[]
            {
                powerbase.ToString(), "to the power of", 
                x.ToString(), "is", 
                Math.Pow(powerbase, x).ToString()
            };
        };
    };
 
    var iterator = PowerStringIterator(2);
 
    Times(10, 
        () => Output(iterator()));
}
Output:

2 to the power of 1 is 2
2 to the power of 2 is 4
2 to the power of 3 is 8
2 to the power of 4 is 16
2 to the power of 5 is 32
2 to the power of 6 is 64
2 to the power of 7 is 128
2 to the power of 8 is 256
2 to the power of 9 is 512
2 to the power of 10 is 1024

Legg spesielt merke til høyereordens-funskjonen PowerStringIterator: Den er en slags factory som returnerer en ny funksjon som er en closure rundt variabelen x, og som dermed kan brukes som en iterator som gir meg en uendelig rekke med output (hvorav jeg skriver ut de 10 første). Variabelen x er “ute av scope” når PowerStringIterator returnerer, og det er ingen måte å få tak på den igjen på. Men den er like vel ikke borte – den nye iterator-funksjonen bruker den som sin state-variabel, og x blir derfor ikke borte sålenge funksjonen er der og “lukker seg rundt den”.

Wikipedia: A closure is a first-class function with free variables that are bound in the lexical environment. Such a function is said to be “closed over” its free variables. A closure is defined within the scope of its free variables, and the extent of those variables is at least as long as the lifetime of the closure itself. The explicit use of closures is associated with functional programming and with languages such as ML and Lisp. Closures are used to implement continuation passing style, and in this manner, hide state.

Vær obs på at closures ofte brukes feilaktig som et annet ord for anonyme funksjoner (jeg kan selv dokumentere å ha gjort det for ikke så lenge siden). Anonyme funksjoner kan danne closures (i mange språk), men det er likevel to separate begrep.

.NET-rammeverket støtter forresten egentlig ikke “ekte” closures, men simulerer det ved å opprette en klasse som holder på state (variabelen x). Action og Func, lambdaer i .NET, er jo egentlig klasser/objekter. Tar vi en titt på den kompilerte assemblien med Reflector finner vi klassen som er gjengitt nedenfor. Kompilatoren har generert denne for å representere iterator-funksjonen som PowerStringIterator returnerer:

[CompilerGenerated]
private sealed class <>c__DisplayClassa
{
    // Fields
    public int powerbase;
    public int x;

    // Methods
    public IEnumerable<string> <Main>b__3()
    {
        this.x++;
        return new string[] {
          this.powerbase.ToString(),
          “to the power of”,
          this.x.ToString(),
          “is”,
          Math.Pow((double) this.powerbase, (double) this.x).ToString()
        };
    }
}

Men nok om closures, og over til Clojure (som faktisk uttales closure!). Nedenfor har jeg implementert det samme programmet – strukturelt sett er det identisk med C#-varianten, så sammenlign dem gjerne for å lære litt mer om Clojure/Lisp.

(defn times [n action]
  (dotimes [_ n] (action)))

(defn output [args]
      (doseq [s args]
             (print s “”))
      (newline))

(defn power-string-iterator [powerbase]
      (let [x (atom 0)]
        (fn []
            (reset! x (inc @x)) ; mutate x
            (list powerbase “to the power of” @x “is” 
                  (int (Math/pow powerbase @x))))))

(def iterator (power-string-iterator 2))

(times 10 
       (comp output iterator))

Merk at jeg normalt ikke ville ha definert funksjonene times og output – disse er her for å gjøre eksempelet likt C#-varianten, og tilsvarende funksjoner finnes allerede i Clojure-språket.

Fordi jeg ønsker å mutere (endre verdien på..) x har jeg valgt å opprette det som et atom. Du kan lese mer om det her.

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.

Jeg vet ikke.., inspirerte dette til noen nye tanker? Ser du at C#-koden er “lispy”? Om du ikke skjønte noe mer Lisp av dette så fikk jeg i alle fall endelig snakket litt om hva closures er for noe, så jeg er fornøyd :P

Kategorier: LISP/Clojure, Polyglot.
RSS feed for kommentarene. Tilbaketråkk.

3 kommentarer til “Lispy C# (og hva er en closure)”

  1. Bjarte Says:

    Hadde aldri tenkt over at man kan gjøre closures på den måten i C#. Har forsåvidt aldri hatt bruk for det heller, men likevel. Spennende.

  2. Torbjørn Says:

    Nei, når man jobber med klassebasert objektorientering er det sjelden man tenker at “her skulle jeg hatt en closure”. I JavaScript, som jeg vet du jobber endel med, er det vel en mere vanlig teknikk har jeg fått inntrykk av? Det som er interessant er at closures kan gi ikke-OO-språk objektorienterte egenskaper. De kan også brukes til å implementere nye kontrollstrukturer (branching, løkker og slikt). Men det er kanskje for spesielt interesserte..

  3. Message Passing Style Says:

    [...] Jeg har stilt og forsøkt å svare på dette tidligere i blogposten Lispy C#. En “lexical closure” er en funksjon pluss en referanse til de variablene som brukes i funksjonen men som ikke er definert der – såkalte frie variabler. Selv om variablene faller ut av scope, vil de fortsatt være tilgjengelig i funkjonen, fordi closuren holder referanser til dem. [...]

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