Jeg leker litt med Clojure i .NET


onsdag 3. november 2010 Clojure

ClojureCLR

Nå har jeg endelig fått testet ut ClojureCLR, dvs. Clojure kjørende på .Net-rammeverket. Det er kjempeenkelt å komme igang – du bare laster ned en zip med bits herfra (anbefaler clojure-clr 1.2.0), pakker det ut et sted, starter et command-vindu og eksekverer Clojure.Main.exe. Hvis du ikke sender med en clojurekildefil som argument startes den interaktive REPL'en (Read Eval Print Loop) hvor du kan leke deg med språket.

CropperCapture[87]

Jeg jobber mye med MSMQ, og da jeg skulle teste ut Clojure i .NET føltes det derfor naturlig å starte med det. Denne blogposten består av noen eksempler på hvordan man kan bruke System.Messaging og meldingskøer fra ClojureCLR – og illustrerer dermed .NET-interopen, og hvordan den eventuelt skiller seg fra Java-interopen i "vanlig" Clojure.

Jeg begynner med en tom fil jeg kaller testqueue1.clj. Det første jeg må gjøre er å laste System.Messaging dll'en, og gjøre klassene jeg har tenkt å bruke tilgjengelige. Det gjør jeg på denne måten:

1 (System.Reflection.Assembly/LoadWithPartialName "System.Messaging")
2 (import '(System.Messaging MessageQueue
3                            MessageQueueTransaction
4                            XmlMessageFormatter))

Jeg har allerede en lokal, transaksjonell kø som heter testqueue, og jeg vil bruke denne fra koden min. Jeg definerer symbolet *queue* til å være en instans av MessageQueue, og setter også Formatter-propertien til en XmlMessageFormatter som håndterer strenger:

6 (def *queue*
7      (doto (MessageQueue. ".\\private$\\testqueue")
8            (.set_Formatter (XmlMessageFormatter.
9                              (into-array Type [String])))))

Opprettelse av objekter og kall av metoder er akkurat som i Clojure for JVM. Der er ingen spesiell syntaks for properties, i stedet bruker man funksjonene som ligger bak propertiene (get_PropertyName og set_PropertyName). I linje 9 ser du også hvordan jeg konverterer en PersistanceVector om til et array av typen System.Type, som er det konstruktøren til XmlMessageFormatter trenger.

Og nå kan jeg opprette min første funksjon. Denne er en ganske enkel en som returnerer antall meldinger i køen:

11 (defn count-messages []
12       (-> *queue*
13           (.GetAllMessages)
14           (.Length)))

Deretter vil jeg lage én metode for å sende en melding og én metode for å hente ut en melding. Felles for disse to er at jeg trenger å opprette og bruke en transaksjon. For å unngå kodeduplisering lager jeg en "høyereordens funksjon" jeg kaller with-transaction. Den tar som innput en lambda som definerer hva som skjer i transaksjonen.

16 (defn with-transaction [f]
17       (let [transaction (new MessageQueueTransaction)]
18         (.Begin transaction)
19         (let [result (f transaction)]
20           (.Commit transaction)
21           result)))
22
23 (defn send-to-queue [message]
24       (with-transaction
25         #(.Send *queue* message %)))
26
27 (defn receive-one []
28       (with-transaction
29         #(.Receive *queue* (TimeSpan. 0 0 1) %)))

Når jeg utvikler i Clojure bruker jeg den interaktive REPL'en ganske flittig. Typisk jobber jeg i en eller flere tekstfiler samtidig som jeg har en REPL kjørende. I den kan jeg laste filene, og reloade dem når jeg har gjort endringer. REPL'en lar meg test/kjøre funkjonene jeg definerer med ulike innput, eller teste ut kode før jeg skriver den i kildekodefilen. Jeg kan til og med re-definere enkeltfunksjoner underveis om jeg ønsker det.

Nedenfor er et lite eksempel på hvordan jeg kan bruke REPL'en med testqueue1.clj som jeg nettopp har laget. Først laster jeg skriptet. Deretter kaller jeg funksjonene for å telle meldinger, sende melding, og hente ut melding. Legg merke til at jeg velger å lese ut Body-propertien på meldingen jeg leser, selv om receive-one returnerer et Message-objekt.

CropperCapture[88]

Det som skjedde i virkeligheten var at jeg gjorde disse tingene mens jeg definerte funksjonene. Og mellom hver gang jeg gjorde en endring kjørte jeg (require 'testqueue1 :reload) slik at REPL'en ble oppdatert med mine endringer. For et bra innblikk i hvordan det er å jobbe på denne måten anbefaler jeg blogposten Interaktiv programmering: utforsking, læring og produktivitet – skrevet av Thomas Kjeldal Nilsson.

.NET events i ClojureCLR

En annen ting jeg føler jeg bør vise er hvordan man bruker eventer i ClojureCLR. Når man skal bruke meldingskøenes asynkrone funskjonalitet trenger man å sette opp eventhandlere for å motta meldinger. ClojureCLR har en macro som heter gen-delegate for å opprette delegater. I linje 12 nedenfor bruker jeg den til å opprette on-receive-completed. Og i linje 26 ser du hvordan jeg kobler delegatet på eventet. Følgende er plassert i en fil jeg kalte testqueue2.clj:

1 (System.Reflection.Assembly/LoadWithPartialName "System.Messaging")
2 (import '(System.Messaging MessageQueue
3                            MessageQueueTransaction
4                            XmlMessageFormatter
5                            ReceiveCompletedEventHandler))
6
7 (declare handler) ; handler will point to a function later..
8
9 ; generate a delegate of type ReceiveCompletedEventHandler
10
11 (def on-receive-completed
12      (gen-delegate ReceiveCompletedEventHandler
13                    [source async-result]
14                    (let [message (.EndReceive source
15                                               (.AsyncResult async-result))
16                          result (handler message)]
17                      (.BeginReceive source)
18                      result)))
19
20 ; *queue* will have a ReceiveCompleted handler set..
21
22 (def *queue*
23      (doto (MessageQueue. ".\\private$\\testqueue")
24            (.set_Formatter (XmlMessageFormatter.
25                              (into-array Type [String])))
26            (.add_ReceiveCompleted on-receive-completed)))
27
28 ; function to start receiving, given a specific handler function..
29
30 (defn async-receive [handler-fn]
31       (def handler handler-fn)
32       (.BeginReceive *queue*))

Nedenfor ser du hvordan jeg bruker to REPL'er til å teste ut testqueue2. Det øverste vinduet bruker jeg til å sende endel meldinger. I det nederste vinduet setter jeg opp async-receive til å printe ut alle meldinger som kommer inn. Funksjonskallet avslutter øyeblikkelig. Deretter kommer meldingene fortløpende etterhvert som jeg sender dem.

CropperCapture[89]

For flere detaljer, se CLR-Interop wikisiden på GitHub. Clojure er suverent, og etter å ha kjørt det på Java-plattformen i noen måneder føles det nå som å komme hjem når jeg endelig får benytte .NET-rammeverket. :)


comments powered by Disqus