Scheme
A minimal Lisp for thinking clearly.
Scheme is a small, elegant dialect of Lisp created at MIT. Its tiny core and powerful ideas (first-class functions, tail calls, continuations) made it the language of the famous textbook Structure and Interpretation of Computer Programs (SICP). It is a superb language for learning how programming languages and interpreters work.
Why learn Scheme?
Scheme has so few rules that you can learn the whole language quickly and then spend your time on ideas.
- Minimal: a handful of forms build everything else.
- SICP: one of the most respected computer science books uses it.
- Interpreters: writing a Scheme interpreter teaches how languages work.
Set up and run your first program
Install Chez Scheme (fast) or GNU Guile. Racket can also run Scheme code.
sudo apt install chezscheme
scheme --script hello.ss(define name "Ada")
(display (string-append "Hello, AI! Welcome to Scheme, " name "."))
(newline)Expressions and define
Everything is an expression. define names values and functions.
(define layers 32)
(define model "llama3.1")
(define (fp16-gb params) (/ (* params 2) 1e9))
(display model) (newline)
(display (fp16-gb 8.03e9)) (newline) ; 16.06
(display (+ 1 2 3 4)) (newline)Functions and lambda
Functions are values. lambda creates them, and they can be passed to or returned from other functions.
(define (square x) (* x x))
(define (compose f g) (lambda (x) (f (g x))))
(define add1-then-square (compose square (lambda (x) (+ x 1))))
(display (square 12)) (newline)
(display (add1-then-square 4)) (newline) ; 25
(display (map square '(1 2 3 4))) (newline)Lists and recursion
Lists are built with cons; car and cdr take them apart. Recursion is the main way to loop.
(define vram '(("H100" . 80) ("H200" . 141) ("RTX 5090" . 32)))
(define (sum lst)
(if (null? lst) 0 (+ (car lst) (sum (cdr lst)))))
(define (big-gpus gpus)
(cond ((null? gpus) '())
((>= (cdar gpus) 80) (cons (caar gpus) (big-gpus (cdr gpus))))
(else (big-gpus (cdr gpus)))))
(display (sum '(80 141 180))) (newline)
(display (big-gpus vram)) (newline)Tail calls and let
Scheme guarantees tail-call optimisation, so recursive loops run in constant memory. let creates local variables; a named let makes a loop.
(define (sum-to n)
(let loop ((i 1) (acc 0))
(if (> i n) acc (loop (+ i 1) (+ acc i)))))
(display (sum-to 1000000)) (newline)
(let ((w 3) (b 0.5))
(display (+ (* w 2) b)) (newline))A tiny interpreter
Because code is data, a Scheme interpreter for arithmetic fits in a few lines, the classic SICP exercise.
(define (evaluate expr)
(cond ((number? expr) expr)
((pair? expr)
(let ((op (car expr)) (args (map evaluate (cdr expr))))
(case op
((+) (apply + args))
((*) (apply * args))
((-) (apply - args))
(else (error "unknown op" op)))))
(else (error "bad expression" expr))))
(display (evaluate '(+ 1 (* 2 3) (- 10 4)))) (newline) ; 13Scheme and AI
Scheme comes from the MIT AI Lab tradition: symbolic reasoning, search and pattern matching were written in Lisp and Scheme. Today it is used to teach AI fundamentals such as search, logic and automatic differentiation, and GNU Guile can call LLM APIs through its web client.
The essential libraries
Automatic differentiation with dual numbers
The idea behind backpropagation, in a few lines: carry a value and its derivative together.
(define (dual v d) (cons v d))
(define val car) (define der cdr)
(define (d+ a b) (dual (+ (val a) (val b)) (+ (der a) (der b))))
(define (d* a b) (dual (* (val a) (val b))
(+ (* (der a) (val b)) (* (val a) (der b)))))
;; f(x) = 3x^2 + 2x -> f'(x) = 6x + 2
(define (f x) (d+ (d* (dual 3 0) (d* x x)) (d* (dual 2 0) x)))
(define r (f (dual 4 1)))
(display (list 'value (val r) 'derivative (der r))) (newline) ; 56 and 26Call an LLM from Guile Scheme
GNU Guile's built-in web client and the guile-json library can talk to a local model. The tool-calling loop is the same as in other volumes: send, check for tool_calls, reply and repeat.
(use-modules (web client) (web uri) (json) (ice-9 receive))
(define url "http://localhost:11434/v1/chat/completions")
(define body
(scm->json-string
'(("model" . "llama3.1")
("messages" . #((("role" . "user") ("content" . "Explain tensor cores in one sentence.")))))))
(receive (response text)
(http-post (string->uri url)
#:body body
#:headers '((content-type application/json))
#:decode-body? #t)
(let* ((reply (json-string->scm (if (string? text) text (utf8->string text))))
(choice (vector-ref (assoc-ref reply "choices") 0)))
(display (assoc-ref (assoc-ref choice "message") "content"))
(newline)))guile-json represents JSON arrays as vectors and objects as association lists.
ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.Scheme learning roadmap
Tick items off as you go. Your progress is saved in this browser.
Beginner
Intermediate
Advanced
Projects to build
Scheme cheat sheet
| Task | Syntax |
|---|---|
| Define | (define x 5) |
| Function | (define (f x) (* x 2)) |
| Lambda | (lambda (x) (* x 2)) |
| List | (list 1 2 3) |
| Condition | (cond ((> x 0) "pos") (else "other")) |
| Map | (map f lst) |
Common mistakes
- Implementations differ a lot beyond the core standard (R7RS).
- Use
equal?for lists and strings;eq?checks identity. - Deep non-tail recursion can still exhaust memory; prefer named let loops.
Where to go next
- SICP — classic book
- The Scheme Programming Language — free book
- GNU Guile — implementation
On TensorCore.si: Racket volume, Lisp volume.
Keyboard: [ previous language · ] next language · / search the library