Een vriendelijke introductie tot Racket

"Lisp is het waard om te leren vanwege de diepgaande verlichtingservaring die je zult hebben wanneer je het uiteindelijk doorziet." — Eric S. Raymond

Een stukje geschiedenis

Lisp werd in 1958 uitgevonden door John McCarthy aan het MIT. Ter context: het is de op één na oudste high-level taal die nog steeds in gebruik is (alleen Fortran uit 1957 is een jaar ouder). Python verscheen in 1991 en JavaScript in 1995. Lisp gaat hen met meer dan 30 jaar voor en verschillende ideeën die we nu als "modern" beschouwen, zijn daar ontstaan:

  • Garbage collection — uitgevonden voor Lisp.
  • First-class functies — het doorgeven van functies als argumenten, wat nu overal standaard is.
  • De REPL — de interactieve read-eval-print loop die Python, Node en Julia vandaag de dag allemaal hebben, begon in Lisp.
  • Conditionals als expressies — de if die een waarde teruggeeft.
  • Homoiconiciteit — code is een datastructuur van de taal zelf. Dit is het belangrijkste punt; we komen hier aan het einde op terug.

Decennialang was Lisp de taal van kunstmatige intelligentie. In de jaren 70 en 80 waren er fysieke computers die speciaal waren ontworpen om Lisp direct uit te voeren: de Lisp Machines gebouwd door Symbolics en LMI. Daarna kwam de "AI-winter", de financiering droogde op en Lisp veranderde van een ster in een culttaal.

Maar interessante ideeën sterven niet; ze muteren. Dat is onderdeel van de schoonheid van de Lisps.

Van Lisp naar Scheme naar Racket

In 1975 creëerden Gerald Sussman en Guy Steele Scheme: een minimalistische, elegante en bijna wiskundige Lisp. Scheme werd de favoriete taal in de academische wereld voor het onderwijzen van programmeren (het legendarische boek SICP: Structure and Interpretation of Computer Programs is in Scheme geschreven).

In 1995 creëerde de groep van Matthias Felleisen PLT Scheme, een variant van Scheme ontworpen voor onderwijs en onderzoek naar programmeertalen. In 2010 werd dit omgedoopt tot Racket. Tegenwoordig is het veel meer dan een Scheme: het is een taal om talen te bouwen. Het onofficiële motto is language-oriented programming: als jouw probleem een eigen taal vereist, stelt Racket je in staat om er in een middag een te bouwen.

Tijdlijn van ontwikkelingen

JaarGebeurtenis
1958McCarthy vindt Lisp uit aan het MIT
1975Sussman en Steele creëren Scheme
1984Common Lisp wordt gestandaardiseerd
1995PLT Scheme (nu Racket) wordt geboren
2007Clojure wordt geboren (Lisp op de JVM)
2010PLT Scheme wordt omgedoopt tot Racket
VandaagJij leest dit en staat op het punt haakjes te schrijven UwU

Wie gebruikt Lisp vandaag de dag?

Meer mensen dan je zou denken:

  • Clojure draait in productie bij banken, luchtvaartmaatschappijen en startups (Nubank, de grootste digitale bank in Latijns-Amerika, draait op Clojure).
  • Common Lisp (met de SBCL-compiler) is nog steeds springlevend in expertsystemen, vluchtplanning (ITA Software, overgenomen door Google, powered Google Flights) en wetenschappelijke computing.
  • Emacs Lisp — miljoenen mensen voeren elke dag Lisp uit zonder het te weten, omdat hun editor een Lisp-interpreter is.
  • Guile/Guix — een complete Linux-distributie die voor 100% in Scheme is geconfigureerd.
  • Racket heeft zijn eigen jaarlijkse conferentie (RacketCon), een actieve academische en artistieke gemeenschap, en wordt gebruikt voor taalonderzoek, formele verificatie (Rosette), typografie en publicatie (Pollen) en onderwijs wereldwijd.

Daarnaast blijven er nieuwe Lisps verschijnen: Fennel (een Lisp die compileert naar Lua, populair voor games), Janet, Hy (een Lisp bovenop Python)...

Easter egg voor TADC-fans

In The Amazing Digital Circus (aflevering 8, "hjsakldfhl"), wanneer Kinger de terminal opent om Caine te resetten, is te zien dat Caine (een creatieve AI gebouwd in 1996) is geprogrammeerd in Lisp. Het bestand heet letterlijk Caine-core.lisp.

Installatie (5 minuten)

  1. Ga naar https://racket-lang.org.
  2. Download de installer voor jouw systeem (Linux, macOS, Windows).
  3. Open DrRacket, de omgeving die is inbegrepen.

DrRacket heeft twee gebieden: bovenaan schrijf je je definities (je programma), en onderaan heb je de REPL voor live experimenten. Schrijf op de eerste regel van het definitiesgebied:

#lang racket

Die regel vertelt Racket welke taal je gebruikt (denk eraan: Racket is een taalfabriek, dus je moet er een kiezen).

Als je de voorkeur geeft aan de terminal: het commando racket geeft je een REPL, en raco is de package manager en tooling-commando.

Eerste contact: alles is een expressie

Probeer het volgende in de REPL:

> (+ 1 2)
3
> (* 3 (+ 2 2))
12
> (string-append "hello " "world")
"hello world"

De regel van Lisp past in één zin: Alles is (operator argument1 argument2 ...). Altijd. Zonder uitzonderingen.

Er is geen operator-prioriteit die je uit je hoofd moet leren, geen speciale syntax voor specifieke zaken. (+ 1 2) telt op. (if ...) beslist. (define ...) benoemt. De haakjes die in het begin intimiderend lijken, zijn in feite de volledige afwezigheid van arbitraire regels. Na een week stop je ze met het zien.

Definities en functies

#lang racket

(define pi-approx 3.14159)

(define (circle-area r)
  (* pi-approx r r))

(circle-area 2)   ; => 12.56636
  • define met een naam maakt een constante.
  • define met (naam argumenten...) maakt een functie.
  • Commentaren beginnen met ;.

Anonieme functies gebruiken lambda (ja, die lambda uit de lambda-calculus van Church uit de jaren 30 is de theoretische grootouder van dit alles):

(lambda (x) (* x x))          ; een functie zonder naam
((lambda (x) (* x x)) 5)      ; => 25, direct toegepast

Lijsten: het hart van Lisp

Lisp staat voor LISt Processing. Lijsten zijn de fundamentele structuur:

(list 1 2 3)          ; => '(1 2 3)
'(1 2 3)              ; hetzelfde, "quoted"
(first '(1 2 3))      ; => 1
(rest '(1 2 3))       ; => '(2 3)
(cons 0 '(1 2 3))     ; => '(0 1 2 3)
(length '(a b c))     ; => 3

Let op het quote-teken '. Dit vertelt Racket: evalueer dit niet, dit is data. Onthoud dit detail; het is de deur naar de laatste truc.

Higher-order functies

Dit is waar Racket uitblinkt. Het doorgeven van functies aan andere functies is hier zeer natuurlijk:

(map (lambda (x) (* x x)) '(1 2 3 4 5))
; => '(1 4 9 16 25)

(filter even? '(1 2 3 4 5 6))
; => '(2 4 6)

(foldl + 0 '(1 2 3 4 5))
; => 15

map transformeert, filter selecteert, foldl accumuleert. Met deze drie functies kun je de meeste lijstproblemen oplossen zonder een enkele for-loop te schrijven.

Recursie: denken in spiralen

In Lisp denk je niet "herhaal N keer", maar "wat is het basisgeval, en hoe beweeg ik daar naartoe?":

(define (factorial n)
  (if (= n 0)
      1
      (* n (factorial (- n 1)))))

(factorial 5)   ; => 120

En om het visueel te maken, laten we iets tekenen. Racket wordt geleverd met ingebouwde grafische bibliotheken:

#lang racket
(require 2htdp/image)

(define (sierpinski level)
  (if (= level 0)
      (triangle 8 "solid" "purple")
      (let ([t (sierpinski (- level 1))])
        (above t (beside t t)))))

(sierpinski 6)

Plak dit in DrRacket, druk op Run, en zie hoe de Sierpinski-driehoek op je scherm verschijnt.

De grote finale: code die code schrijft

Onthoud het quote-teken ': het verandert code in data. Kijk maar:

'(+ 1 2)          ; => de LIJST (+ 1 2), niet het getal 3
(first '(+ 1 2))  ; => het symbool +
(eval '(+ 1 2))   ; => 3. Je hebt zojuist data geëvalueerd als code.

Je programma is een lijst. Je kunt lijsten bouwen. Daarom: je kunt programma's bouwen met programma's. Dit is homoiconiciteit, en dit is waarom Lisp echte macro's heeft — niet tekst-macro's zoals in C, maar functies die code ontvangen en code teruggeven, voordat er iets wordt uitgevoerd.

Heeft Racket geen while-loop? Laten we er zelf een uitvinden:

(define-syntax-rule (while condition body ...)
  (let loop ()
    (when condition
      body ...
      (loop))))

(define counter 0)
(while (< counter 5)
  (displayln counter)
  (set! counter (+ counter 1)))

Je hebt zojuist de taal uitgebreid! In Lisp is de syntax van jou.

Alan Kay noemde Lisp de "Maxwell-vergelijkingen van software": een kleine kern waaruit alles anders kan worden afgeleid.

Wat nu?

  • How to Design Programs — het boek waar Racket omheen is ontworpen, gratis online beschikbaar.
  • The Racket Guide — officiële documentatie, behorend tot de beste die er zijn.
  • Beautiful Racket — leer hoe je je eigen talen bouwt.
  • SICP — de klassieker der klassiekers, als je de volledige verlichting zoekt.