Lisp for Dummies
- Monday, August 2nd, 2010
- Skriv en kommentar
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..
(add
(inc 1)
(floor 2.5))
(subtract 3 4))
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!
Noen ressurser
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.
Kategorier: LISP/Clojure, Polyglot.
RSS feed for kommentarene.
Tilbaketråkk.



August 2nd, 2010 at 8:37 am
Practical Common Lisp er en fantastisk bok! Peter Seibel er min helt. Det er nok en del nitty-gritty-detaljer der som ikke er så relevante for Clojure, ettersom det har med forskjeller på ulike Common Lisp-implementasjoner å gjøre. Men ellers burde det være en bra bok for deg.
August 2nd, 2010 at 9:33 am
Genialt
August 2nd, 2010 at 10:30 am
Et spørsmål: er det ikke litt snodig at man ikke må quote = i condp? Altså (condp ‘= …)? Eller er det jeg som er noob?
August 2nd, 2010 at 11:24 am
Einar, kan du si hvorfor du synes det er snodig? Du sender inn funksjonen som et argument til condp.., du trenger vel aldri quote når du gjør sånt? Kan hende Clojure skiller seg fra Scheme/CL på det området?!
August 2nd, 2010 at 1:30 pm
Det er selvfølgelig jeg som roter. Jeg tenkte quote => data, no-quote => evaluere, og når du sender en funksjon som en parameter, behandler du jo på en måte funksjonen som data. Men det er en feilslutning. ‘= gir deg naturligvis _symbolet_ =, ikke funksjonen. Det man _kan_ gjøre (i CL/Scheme), er å gjøre en “funksjonsquote” for å få tak i selve funksjonsobjektet: #’=.
I CL kan man da kalle funksjonen = ved å skrive (funcall #’ …). Dette står beskrevet i Seibels bok (http://www.gigamonkeys.com/book/functions.html). I Scheme kan man få til noe lignende vha apply/eval.
Et banalt eksempel (Scheme, verifisert i DrRacket):
1) Ingen quote. Funksjonen kan brukes direkte – som i condp i Clojure.
> (define seventeen (lambda f x) (f x 17)))
> (seventeen = 17)
#t
2) Med funksjons-quote. Funksjonen kan brukes indirekte vha apply/eval.
> (define 7teen (lambda f x) (apply (eval f) (list x 17))))
> (7teen ‘= 17)
#t
Beklager rotet!
August 2nd, 2010 at 1:32 pm
Urk, enda mer rot. Jeg mente å skrive (funcall #’= …) og (7teen #’= 17). Hodet er tydeligvis på ferie fremdeles.
August 3rd, 2010 at 1:48 am
Det er jo fordi CL har forskjellige namespaces for funksjoner og variabler, så hvis det hadde vært CL og du hadde skrevet (condp = …) så hadde den lett etter en variabel med navn «=» og ikke en funksjon. #’= er da for å lete i funksjons-namespacet. I både Clojure og Scheme slipper du dette.
Du kan forresten gjøre det så enkelt som (define (7teen f x) ((eval f) x 17)) i det andre eksemplet ditt.
August 3rd, 2010 at 1:48 pm
Du har helt rett Ameth.
August 3rd, 2010 at 10:21 pm
http://static.nostdal.org/~lnostdal/always-here/lisp-for-dummies.png
Emacs+Slime indenterer forøvrig CL-kode riktig; litt krongel å sette opp, men av flere årsaker enn m.t.p. dette med indentering verdt det om en først skal bruke Lisp.
Lykke til med PCL; det er en fin og morsom bok. :)
August 4th, 2010 at 8:54 am
Lispbox (som beskrevet i PCL) gir deg Emacs+Slime uten hassle.
August 4th, 2010 at 9:00 am
Selv bruker jeg Vim, og den har fin Lisp-indentering (koden i blogpostene mine er direkte html-eksport derfra). Men jeg må innrømme at jeg er litt misunnelig på Emacs+Slime etter å ha sett noen screencasts..
August 8th, 2010 at 4:03 pm
Torbjørn:
Jeg linket til et bilde i posten over; det kom kanskje ikke så klart frem:
http://static.nostdal.org/~lnostdal/always-here/lisp-for-dummies.png
Det kan forøvrig hende at det er vanlig med en _noe_ annerledes stil i sammenheng med Clojure, men dette skal være riktig i sammenheng med Scheme og CL ihvertfall.
Hvorvidt ting følger på en vertikal rett linje nedover slik som i eksempelet med (multiply …) eller om første argument på egen linje “trekkes inn” slik som i sammenheng med (defun …) baserer seg gjerne på bruk av &BODY. F.eks. brukes &BODY i DEFUN og i (brukerdefinerte) macroer some f.eks. WHILE o.l..
En ser at i sammenheng med IF brukes ikke &BODY derfor er argumentet på første nye linje ikke “trukket inn”, men følger på en vertikal rett linje nedover slik som i eksempelet med (multiply …).
Slike ting tar Emacs+Slime seg av automatisk; om en definerer en ny macro med eller uten bruk av &BODY vil Emacs+Slime holde styr på dette og indentere riktig alt-ettersom.
PS: Hadde egentlig tenkt å maile deg, men fant ikke noen e-postaddresse.
August 8th, 2010 at 10:40 pm
Hei Lars Rune. Jeg så bildet første gangen, men følte i første omgang at å si at jeg brukte Vim’s Lisp-indentering var gått nok svar.
Du sier at det kun er én riktig måte å indentere på i Lisp. Om du ikke kan referere til noe i Lisp-spesifikasjonen som sier noe annet vil jeg hevde at det ikke finnes noen regler for hvordan Lisp skal indenteres! Det finnes derimot praksiser, og som regel skyldes disse praksisene hvordan editoren som brukes mest (emacs) oppfører seg. Det du sier er verdifull input, men sålenge jeg bruker Vim vil jeg nok stort sett være fornøyd med hvordan den bestemmer at Lisp’en min skal se ut.
Din bruk av linjeskift er finere enn min; jeg gjorde det som jeg gjorde for å illustrere, og bryter normalt opp noe anderledes. Jeg føler derimot ikke at jeg følger noen regler, men gjør det som resulterer i mest mulig lesbar kode.
For ordens skyld: Jeg bruker “Lisp” indenteringsregler, jeg har ikke installert de Clojure-spesifike reglene/syntaksdefinisjonene. Dette er nok for eksempel årsaken til at funksjonene mine ikke har riktig indentering, siden Vim ikke skjenner til defn (men bare defun).
Jeg stusser dog litt over at Vim indenterer ‘if’ slik som du sier ‘while’ skal indenteres, og ‘while’ slik du sier ‘if’ skal indenteres. :|
August 8th, 2010 at 11:14 pm
Hei,
Ja, jeg kjenner ikke til noe snakk om dette i Hyperspec’en f.eks. ( http://www.lispworks.com/documentation/HyperSpec/Front/ ), så dette er nok snakk om tradisjon og praksis(#1).
M.t.p. “én riktig måte å gjøre ting på” mener jeg spesielt ting relatert til bruk av Tab(#2) og intenderingen i sammenheng med DEFUN / WHILE; altså &BODY. Det er kanskje _noe_ variasjon m.t.p. antall mellomrom(#3), men første argument er alltid skjøvet noe til venstre og _ikke_ rett under første argument til f.eks. DEFUN — dette slik at videre kode ikke tilslutt ender opp for langt til høyere på skjermen ettersom en nester flere ting noe som er lett (og morsomt!) å gjøre i Lisp siden mer eller mindre alt kan returnere verdier! :)
Happy hackin’!
#1: ..noe som forøvrig er uavhengig av editor. :)
#2: ..aner ikke om VIM setter in Tab av seg selv eller hva.
#3: ..men uten å undersøke så fryktelig nøye bruker all Lisp-kode jeg kommer på sånn i farta 2 mellomrom her.
August 9th, 2010 at 6:44 am
Lars Rune, jeg er enig i det du sier, bortsett fra det at praksiser ikke er uavhengig av editor. Historien vår er full av praksiser som skyldes editor, og som i utgangspunktet ikke har noe med språket å gjøre.
Ta for eksempel Erlang: Der er det vanlig å begynne kommentar-linjer med %%. Erlang-spekken sier man bare skal bruke én %, men fordi den som lagde Emacs-pluginen for Erlang valgte å bruke to stykker har det blitt praksis for alle. I dag skriver folk %% selv om de for eksempel bruker Vim, uten å ane hvorfor.
Når det gjelder kodekonvensjoner som antall tegn for å representere innrykk så bør det alltid være opp til den enkelte, eller det enkelte teamet om vi snakker om prosjekter/open source. Det finnes for eksempel mange som har problemer med å se innrykk på bare 2 tegn.
Dessuten må vi huske på at vi har endel mer skjermplass tilgjengelig i dag som vi kan utnytte enn man hadde for noen år siden, og at mange konvensjoner ble skapt mens man hadde 10 linjer og noen-og-70 kolonner (eller noe sånn) på en skjerm. Større innrykk er nok bare en vanesak (men mine Vim-instillinger er på 2 tegn de også).