Lisp for Dummies

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!

lisp_for_dummies

Begynn med et vanlig metodekall i ditt favoritt-språk med C-lignende syntax:

add(1, 2);

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.

(add 1, 2);

2: Semikolon er for pyser, så fjern det.

(add 1, 2)

3: Komma likeså.., helt unødvendig (du kan faktisk beholde komma der om du vil, Clojure tolker komma som whitespace).

(add 1 2)

La oss ta et litt mer komplisert eksempel:

multiply(add(inc(1), floor(2.5)), subtract(3, 4));

På en-to-tre blir dette forvandlet til vakker Lisp:

(multiply (add (inc 1) (floor 2.5)) (subtract 3 4))

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.

lists_breakdown

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

(multiply
 (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.

15 kommentarer til “Lisp for Dummies”

  1. Einar W. Høst Says:

    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.

  2. Bjarte Says:

    Genialt

  3. Einar W. Høst Says:

    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?

  4. Torbjørn Says:

    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?!

  5. Einar W. Høst Says:

    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!

  6. Einar W. Høst Says:

    Urk, enda mer rot. Jeg mente å skrive (funcall #’= …) og (7teen #’= 17). Hodet er tydeligvis på ferie fremdeles.

  7. Ameth Says:

    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.

  8. Einar W. Høst Says:

    Du har helt rett Ameth.

  9. Lars Rune Nøstdal Says:

    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. :)

  10. Einar W. Høst Says:

    Lispbox (som beskrevet i PCL) gir deg Emacs+Slime uten hassle.

  11. Torbjørn Says:

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

  12. Lars Rune Nøstdal Says:

    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.

  13. Torbjørn Says:

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

  14. Lars Rune Nøstdal Says:

    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.

  15. Torbjørn Says:

    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å).

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