mandag 2. august 2010 Lisp Clojure
Har du problemer med å forstå Lisp? Blir du svimmel allerede på parantes nummer tre? Ser det rett og slett så merkelig ut at du ikke engang aner hvor du skal begynne? Fortvil ikke, her følger ALT du behøver å vite for å forstå Lisp-kode!
Begynn med et vanlig metodekall i ditt favoritt-språk med C-lignende syntax:
Du kan nå forvandle dette metodekallet til perfekt Lisp-kode med tre enkle steg. Lispen jeg bruker her er Clojure, men reglene er nokså universelle.
1: Flytt start-parantesen forran metodenavnet.
2: Semikolon er for pyser, så fjern det.
3: Komma likeså.., helt unødvendig (du kan faktisk beholde komma der om du vil, Clojure tolker komma som whitespace).
La oss ta et litt mer komplisert eksempel:
På en-to-tre blir dette forvandlet til vakker Lisp:
Og det er omtrent alt du trenger å kunne. All Lisp er bygd opp på denne måten; to paranteser med et funksjonskall og en rekke argumenter innenfor.
Lisp bryr seg ingen ting om linjeskift og indentering, men det gjør utviklere som skal lese koden. Så for å gjøre Lisp-koden enklere å lese legger vi normalt på litt av det..
Det er nå ganske lett å se hvilke to ting som multipliseres sammen, og hvilke to ting som adderes sammen.
I "vanlige" programmeringsspråk har vi operatorer som +, –, * og /. De er også egentlig funksjoner, men vi får lov til å bruke infix-notasjon, og da ser det noe anderledes ut. I Lisp tillater vi ALDRI infix-notasjon, operatorer/funksjoner brukes alltid i prefix-form. Og med ett ble Lisp dobbelt så enkelt som andre språk :)
Operatorer i C | Lisp-kode | |
1 + 2 + 3 + 4;
(10 * 3.14) / 2.5; |
(+ 1 2 3 4)
(/ (* 10 3.14) 2.5) |
I C-lignende språk har vi også diverse kontroll-strukturer som if, for, while etc. I Lisp er også alle disse funksjoner, og de følger de samme syntax-reglene som vi har snakket om sålangt. Jeg sa jo jeg hadde fortalt ALT du trengte å vite for å forstå Lisp!
En typisk if-else | Lisp's versjon av if-else | |
if (n > 0) {
doSomething(n); } else { doSomeOtherThing(n); } |
(if (> n 0)
(doSomething n) (doSomeOtherThing n)) |
I feltet til høyre ser du at if er en funksjon som tar tre parametre: En sannhetstest, et then-uttrykk, og et else-uttrykk. Hvis sannhetstesten evaluerer til true vil if-funksjonen evaluere og returnere then-uttrykket. Hvis ikke vil den evaluere og returnere else-uttrykket.
Så det er altså ikke så vanskelig å skjønne hvordan den fungerer. Det som er vanskelig å skjønne etterhvert er hvorfor man har innebygde spesial-strukturer for slike ting i andre språk…
En switch-case | Clojure's condp | |
switch (language){
case "en": return "Hello"; case "es": return "Hola"; case "dk": return "Hej"; case "ru": return "Privet"; case "it": return "Ciao"; default: return "Hi"; } |
(condp = language
"en" "Hello" "es" "Hola" "dk" "Hej" "ru" "Privet" "it" "Ciao" "Hi") |
Clojure's versjon av den kjente switch-case-strukturen er enda mer genial. Condp tar først inn et predikat (altså en funksjon som returnere true eller false) og et uttrykk som skal brukes til sannhetstesting. Deretter sender man inn så mange argumenter man vil – i par, hvor det første i hvert par brukes til å evaluere predikatet og finne ut om det andre elementet i paret skal evalueres og brukes som returverdi.
Hvis du ikke skjønte det så er altså = (erlik) en funksjon, og er første argument til condp i dette eksempelet. Det er altså ikke en del av en spesialstruktur, og har heller ikke noe med tilordning å gjøre – ting man kanskje kunne tippe om man kommer fra C-lignende språk.
En uendelig løkke | Clojure's versjon av while | |
while(true) {
Console.WriteLine("To Infinity and Beyond"); } |
(while true
(println "To Infinity and Beyond")) |
Igjen er while i Clojure ikke annet enn en funksjon. Den tar to argumenter; et sannhetsuttrykk, og et annet uttrykk som evalueres igjen og igjen sålenge det første evaluerer til true.
Antagelig begynner du å tro meg nå – Lisp har en genialt enkel struktur som brukes til ALT. Som et siste bevis kan vi se på deklarering av variabler (eller verdier som vi sier i funksjonelle språk) og funksjoner.
Deklarasjon av variabel med initiell verdi | Definisjon av verdi med initiering | |
int i = 0;
|
(def i 0)
|
Def er også en funksjon. Første argument er et symbol (i) som skal representere verdien. Andre argument er et valgfritt uttrykk (0), og hvis det er der evalueres det, og symbolet vil nå representere resultatet av uttrykket.
En enkel metode | En enkel funksjon | |
int Double(int n) {
return n * n; } |
(defn double [n]
(* n n)) |
Defn er (du gjettet det) også en funksjon. Her tar den tre argumenter: et symbol som representerer funksjonen, et array med argumentene funksjonen tar, og et uttrykk som evalueres når funksjonen kalles.
PS: Jeg snakker hele tiden om funksjoner, men i Lisp/Clojure snakker vi egentlig om tre ulike ting: funksjoner, makroer og "special forms". Når vi bruker dem forholder vi oss derimot til dem alle på samme måte.
Det var det! Du skal nå være klar til å lese og forstå Lisp. Ta gjerne en tur innom noen av mine tidligere poster om Lisp/Clojure og se om det gir mere mening nå. Lykke til!
For å komme igang med Clojure har jeg først og fremst brukt artikkelen Clojure – Functional Programming for the JVM av R. Mark Volkmann, som er en meget god gjennomgang. API-dokumentasjonen og diverse informasjon på clojure.org har også vært nyttige. I tillegg har jeg lest en interessant blog-serie hvor Clojure sammenlignes med Common Lisp.
Og så et par ressurser jeg ikke har lest enda. Pascal Costanza's Highly Opinionated Guide to Lisp virker ganske interessant, og skal leses straks. Deretter vil jeg gå igang med boken Practical Common Lisp, som er tilgjengelig online i sin helhet. Håpet er at jeg kan overføre det jeg leser der direkte til Clojure, selv om det handler om Common Lisp.