Scheme — Reference
Source: https://small.r7rs.org/
Scheme
- Created: 1975 by Guy L. Steele Jr. and Gerald Sussman at MIT (out of the Lambda Papers).
- Latest stable spec: R7RS-small (2013) — the current actively-recommended language standard. R7RS-large is the in-progress library superset (Working Group 2; published as a series of “Red/Tangerine/Orange/Yellow/Green/…” docket dockets at https://small.r7rs.org/). R6RS (2007) was contentious; R5RS (1998) is the classic minimalist spec still implemented widely.
- Paradigms: Functional-first, lexically-scoped Lisp; supports imperative, OO (via libraries), and FP. The minimalist Lisp.
- Typing: Dynamic, strong, latent. Some implementations layer on optional static typing (Typed Racket — but Racket has diverged; Chez has internal but not public type system).
- Memory: Garbage collected; specifics per implementation (Chez: generational + remembered sets; Guile: BDW conservative; Chicken: Cheney-on-the-MTA).
- Compilation: Wide variation by implementation — Chicken compiles to C, Chez/Gambit to native, Guile to its own bytecode + JIT, Racket (no longer “just a Scheme”) to Chez. Tail calls are guaranteed by the spec (every Scheme implementation must TCO — distinct from Common Lisp).
- Primary domains: Teaching (SICP, HtDP), language research, embedded scripting (Guile in GNU tools — Guix, Lilypond, GIMP-historical), systems programming (Chicken, Gambit, Gerbil), build systems (Guix), VM hosting (Kawa on JVM).
- Notable implementations: Chez Scheme (Cisco/Indiana — fastest, nanopass compiler), Racket (separate language now, but historically Scheme’s flagship; see Racket), Guile (GNU’s, embedded scripting), Chicken (Scheme→C compiler, eggs ecosystem), Gambit/Gerbil (Scheme→C, very fast), Chibi-Scheme (tiny embeddable, R7RS reference impl), MIT/GNU Scheme (the original), Kawa (JVM), Cyclone (Cheney-on-the-MTA, R7RS), Larceny, Bigloo, Sagittarius, Stklos.
- Official docs: https://www.scheme.org/, R7RS-small spec at https://small.r7rs.org/
At a glance
Scheme is the minimalist Lisp: small kernel, lexical scope, first-class continuations, guaranteed TCO, hygienic macros — built around a “more is less” design ethos. The flip side: every implementation diverges past the spec. Use R7RS-small as the portability target; reach for implementation-specific stdlibs/SRFI when needed. SRFIs (Scheme Requests for Implementation, https://srfi.schemers.org/) are the cross-implementation library standard — most modern impls support 50–200 SRFIs. The flagship pedagogical text is Structure and Interpretation of Computer Programs (Abelson & Sussman, MIT Press) — still in print, still defining.
Getting started
Install — pick an implementation:
- Chez Scheme (recommended for performance):
brew install chezscheme/apt install chezscheme/ source at https://cisco.github.io/ChezScheme/. - Guile (most embedding/scripting):
apt install guile-3.0,brew install guile. Default on most Linux distros. - Chicken (binaries + eggs): https://www.call-cc.org/,
apt install chicken-bin. - Gambit/Gerbil (systems): https://gambitscheme.org/ , https://cons.io/.
- Chibi (tiny, R7RS): https://github.com/ashinn/chibi-scheme.
Hello world (hello.scm):
(import (scheme base) (scheme write)) ; R7RS
(display "Hello, world!")
(newline)R5RS legacy form: (display "Hello, world!")(newline) with no import.
Run:
- Chez:
scheme hello.scmorscheme --script hello.scm - Guile:
guile hello.scm - Chicken:
csi -s hello.scm(interpreter) orcsc hello.scm && ./hello(compiled) - Chibi:
chibi-scheme hello.scm
Project layout — Per-implementation. Common patterns:
-
R7RS define-library:
(define-library (my-app util) (export greet) (import (scheme base)) (begin (define (greet name) (string-append "Hello, " name)))) -
Chicken uses eggs (
chicken-install); recipes inmypkg.eggfiles. -
Guile uses module form:
(define-module (my app util) #:export (greet)). -
Gerbil projects use
gxpkg+build.ss.
Package managers:
- Chicken:
chicken-install <egg>(https://eggs.call-cc.org/) - Guile: Guix is the package manager (Guile is itself written for Guix); also CPAN-style via raw modules.
- Snow (https://snow-fort.org/): cross-implementation R7RS package manager.
- Akku (https://akkuscm.org/): R6RS/R7RS package manager.
- Gerbil:
gxpkg(built-in).
REPL — Each implementation has its own (scheme, guile, csi, gosh, chibi-scheme). Editor support: Geiser (Emacs, multi-impl), Quack (older), Slimv for Vim.
Basics
Types/literals — Numbers form a tower: integer → rational → real → complex; with exact (1/3, 42) vs inexact (0.333, 1.0) distinction. Booleans #t/#f (only #f is false). Symbols 'foo. Strings "abc" (length-prefixed). Characters #\a, #\newline. Pairs/lists '(1 2 3), vectors #(1 2 3), bytevectors #u8(1 2 3) (R7RS). Records via define-record-type. The empty list '() is its own value (NOT false in R5RS+, distinct from #f).
Variables/scoping — define (top-level or internal), let (parallel binds), let* (sequential), letrec (mutually-recursive), let-values, parameterize (dynamic scope on parameters created with make-parameter). Lexical scope — closures capture by reference. set! mutates.
Control flow — if, cond, case, when/unless (R7RS), and/or (short-circuit). Loops via tail recursion (always TCO’d). Named let is the idiomatic loop:
(let loop ((i 0) (acc 0))
(if (= i 10) acc (loop (+ i 1) (+ acc i))))Pattern matching via match (SRFI 200/204; built-in to Racket, Guile, Chicken, others).
Functions — (define (f x) ...) is sugar for (define f (lambda (x) ...)). Variadics: (lambda args ...) (all in args) or (lambda (a b . rest) ...). Optional/keyword args are not in R7RS-small — implementations differ (Guile uses define* from (ice-9 optargs), Chicken uses (use srfi-89), Racket has its own). Multiple return values: (values 1 2) + call-with-values or let-values / define-values. Continuations are first-class: (call-with-current-continuation k) (alias call/cc).
Strings & interpolation — No native interpolation. string-append, (format #f "~a + ~a = ~a" 1 2 3) (SRFI 28; full Common-Lisp-style format in SRFI 48/159). (number->string n), (string->number s). SRFI 13 for richer string ops, SRFI 14 for char sets.
Collections — Lists are linked. Vectors random-access. Hash tables: implementation-specific (R7RS-small omits them; SRFI 69, SRFI 125, or (scheme hash-table) in R7RS-large Red Edition). (scheme list) (R7RS-large) and SRFI 1 (the canonical list library) provide fold, filter-map, take, drop, concatenate, iota.
Intermediate
Type system depth — Standardly: dynamic only. Some implementations bolt on:
- Typed Racket (sound, occurrence typing) — but Racket has diverged from Scheme.
- Bigloo has a static type system (
::int,::pair). - Stalin does whole-program type inference for aggressive optimization.
- For portable code: discipline + contracts (SRFI 145 assumes; SRFI 145 Sparkle).
Modules — Format depends on era:
- R7RS
define-librarywithimport/export/include/cond-expand. - R6RS
libraryform. - Implementation-specific historically: Guile
define-module, Chickenmodule, Chezlibrary(R6RS). Usecond-expandfor portability:
(cond-expand ((library (chibi)) (import (chibi))) (else))Error handling — R7RS adds exceptions: (raise obj), (with-exception-handler handler thunk), (guard (var (test1 expr1) ...) body ...). R6RS has condition objects (&condition, &error, etc.). Continuations historically substituted: (call/cc (lambda (k) ...)) to non-locally exit. Most production code uses guard / with-exception-handler today.
Concurrency primitives — Not in R7RS-small. Implementation-specific:
- Guile: native POSIX threads,
(ice-9 threads), fibers ((fibers)lib for go-style CSP). - Chez: threads in
(chezscheme);(make-thread),(make-mutex). - Chicken: cooperative SRFI-18 threads.
- Gambit: lightweight threads (millions per process),
thread-start!, mailboxes. - Racket: green threads, channels, places, futures (see Racket).
- SRFI 18 (multithreading), SRFI 21 (real-time multithreading) attempt portability.
File I/O & networking — (open-input-file path), (call-with-input-file path proc), (read-line port), (read-char port). R7RS adds read-string, read-bytevector. Networking is not in the spec — Guile ((rnrs io ports) + (ice-9 sockets)), Chicken (tcp egg), Gambit (open-tcp-client), Racket ((require racket/tcp)).
Stdlib highlights — by source:
- R7RS-small base:
(scheme base),(scheme case-lambda),(scheme char),(scheme complex),(scheme cxr),(scheme eval),(scheme file),(scheme inexact),(scheme lazy),(scheme load),(scheme process-context),(scheme read),(scheme repl),(scheme time),(scheme write). - R7RS-large editions (in flight): Red (
list,vector,hash-table,set,bag), Tangerine (numerics, divisions), Orange (more numerics), Yellow (text — strings/chars revisited), Green (threading/streams), and continuing. - SRFIs: 1 (lists), 13 (strings), 14 (char sets), 18 (threads), 26 (
cut/cutepartial application), 27 (random), 39 (parameters), 41 (streams), 64 (testing), 69 (hash tables), 145 (assumptions), 158 (generators+accumulators), 197 (pipelinechainoperator).
Advanced
Memory & GC — Implementation-specific. Chez: generational copying with remembered sets; very fast. Chicken: famously, uses the C stack as the nursery via Cheney-on-the-MTA — every Scheme call grows the C stack until it triggers a stack-as-heap collection that copies live data to the real heap (Henry Baker’s idea); allows full TCO + first-class continuations from C. Gambit: precise generational. Guile: BDW (Boehm-Demers-Weiser) conservative GC.
Concurrency deep dive — Mostly per-impl. Gambit is famous for cheap green threads (used in Termite, an Erlang-like distribution layer for Gambit). Guile fibers (https://github.com/wingo/fibers) is a CSP library based on Andy Wingo’s Concurrent ML port. Chez has true OS threads with mutex/condvar/foreign integration.
FFI/interop:
- Chicken:
(declare (foreign-declare ...))and(foreign-lambda ...)— the C compiler is part of the toolchain so you can splice raw C. - Guile:
(system foreign),(system foreign-library)— libffi-baseddynamic-pointer/dynamic-func/pointer->procedure. - Chez:
foreign-procedure,foreign-callable. - Gambit:
(c-define-type),(c-lambda), embeddable into C. - Kawa: full JVM interop (call any Java class).
Reflection — eval is in the spec ((eval expr (interaction-environment))). Per-impl introspection: Chicken’s chicken.base#define-record-type introspection, Guile’s (system base compile) to get IR. Macro inspection via expand (Racket), expand-once (Chez).
Performance tools — Per-impl. Chez: (time expr), (profile expr), the nanopass compiler dumps IR per pass via nanopass-debug. Chicken: csc -profile. Gambit: compile-time deep optimizations + gambit-profile. Guile: ,profile and ,trace REPL meta-commands.
God mode
syntax-rules — pattern-template hygienic macros (R5RS/R7RS-small):
(define-syntax swap!
(syntax-rules ()
((_ a b) (let ((tmp a)) (set! a b) (set! b tmp)))))Hygiene is automatic — tmp cannot collide with a/b even if they happen to be named tmp. Pattern language: ... for ellipsis matching/templating, (literal pat) for literal-symbol matching. Tail-recursive macros via continuation-passing-macro style.
syntax-case — programmable hygienic macros (R6RS):
More expressive: gives you the AST as a syntax object, supports arbitrary Scheme code in the transformer, fenders (additional guards), datum->syntax and syntax->datum to break or recover hygiene intentionally:
(define-syntax my-when
(lambda (stx)
(syntax-case stx ()
((_ test body ...) #'(if test (begin body ...) (if #f #f))))))Available in R6RS impls (Chez, Larceny) and as (rnrs syntax-case) library.
call/cc and delimited continuations — call-with-current-continuation captures the entire rest of the computation as a callable function (one-shot use is most common; multi-shot is wild). Delimited continuations: shift/reset (Filinski/Danvy) capture a delimited slice — vastly easier to reason about, used to implement coroutines, monads, generators, web-app continuations:
(reset (* 2 (shift k (k 10) (k 20)))) ; ⇒ both 20 and 40 returned in sequenceAvailable in Racket, Guile (via (ice-9 control)), Chez via SRFI 165, Chibi via the spec-allowed mechanism.
Tail-call optimization is mandatory — Every call in tail position must reuse the caller’s frame. This is what makes Scheme’s iteration model ((let loop () ... (loop))) viable without stack overflow. Distinct from Common Lisp (where TCO is implementation-dependent) and JS/Python (no TCO).
Hygienic vs non-hygienic macros — The big philosophical fork from Common Lisp. Scheme macros never accidentally capture caller-local names; CL macros routinely do unless you gensym. Trade-off: Scheme macros are safer but historically harder to write (until syntax-parse in Racket). Modern impls support both (syntax-rules for pure hygiene, syntax-case + datum->syntax for controlled un-hygiene).
R5RS vs R6RS vs R7RS-small vs R7RS-large —
- R5RS (1998, ~50pp): the classic minimal Scheme everyone learned from. Few libraries, no module system in spec.
- R6RS (2007, ~160pp): big standard with libraries, conditions/exceptions, Unicode, bytevectors,
syntax-case. Controversial — some major implementors refused (Chez supports; Chicken did not adopt). - R7RS-small (2013): a deliberately small re-do, retreating from R6RS’s scope. Adds
define-record-type,define-library, exceptions (raise/guard), parameter objects, bytevectors. The current portability target. - R7RS-large: WG2 ongoing — assembling editions (“Red”, “Tangerine”…) of agreed libraries (lists, hash-tables, sets, generators, …). Most are SRFIs lifted into the standard.
Chicken Scheme — Scheme to C — Each top-level form compiles to a C function (literally csc foo.scm produces foo.c then foo). Cheney-on-the-MTA enables this: every call grows the C stack, and minor GC scrubs it back. Eggs (https://eggs.call-cc.org/) provide ~1500 packages. Used for Termite (distributed actors), CHICKEN itself bootstrap, real shipping software.
Guile embedding (powering Guix, Lilypond, GnuCash) — Guile is GNU’s blessed extension language. libguile exposes scm_init_guile, scm_c_eval_string, scm_call_n. Apps embed Guile as an extension/scripting layer; Guix (the package manager + OS) is almost entirely Guile, including its build descriptions. Andy Wingo’s blog (https://wingolog.org/) is the canonical source on Guile internals.
Gambit/Gerbil systems programming — Gambit is an aggressive Scheme→C compiler with low-overhead green threads (millions per process). Gerbil (https://cons.io/) layers a modern module system, R7RS, syntax-case, and Erlang-inspired actors on top — used in production for systems software.
Chez Scheme nanopass compiler architecture — Chez’s compiler is the textbook example of the nanopass framework (Sarkar/Waddell/Dybvig 2004): hundreds of tiny passes, each doing a single grammar transformation, with explicit IR languages between every pass. The nanopass framework itself (https://nanopass.org/) is a Racket library for building such compilers — used in pedagogy (“Compilers for Programmers”, Mike Wand at Northeastern).
Idioms & style
- Naming:
kebab-casefor everything;?for predicates (null?,pair?);!for mutation (set!,vector-set!);->for converters (number->string);*foo*for parameters/specials in some traditions but plainfooin others. - Formatter:
scheme-modein Emacs auto-indents per the Lisp conventions; scmindent (https://github.com/ds26gte/scmindent) is the cross-editor reformatter. - Linter: implementation-specific. chez-stats, scheme-langserver. Most rely on the compiler’s warnings + tests.
- Idiomatic: tail recursion (always — never
for/whileloops in canonical Scheme); small composable functions; SRFI 1 list combinators;condover nestedif;letoverset!;parameterizeover global mutation; macros only when a function won’t do (less freely than CL);define-record-typefor fixed-shape data. - Reviewer focus: missing tail position (broken TCO is a bug); macro hygiene leaks;
set!where pure would work; hardcoded implementation features (usecond-expandfor portability); ignoring exact/inexact distinction; not closing ports (usecall-with-input-file, not rawopen-input-file).
Ecosystem
- Web: Spiffy (Chicken), Hunchentoot-equivalents thin on the ground; Artanis (Guile, MVC), Spheres (Gambit), Sagittarius + own server.
- DB: Chicken eggs for postgres/mysql/sqlite3; Guile-DBI; Gerbil-DBI.
- Numerics: Bigloo for fast typed code; Stalin for whole-program optimization; bindings to BLAS/GSL via FFI.
- Build/dist: Guix (the most ambitious — entire Linux distro described in Guile Scheme), Akku, Snow, Chicken eggs, gxpkg (Gerbil).
- Testing: SRFI 64 (cross-impl portable test API), chickadee/test (Chicken), (srfi 64) in most R7RS impls.
- Docs: Scribble (Racket but used by Chicken
/api), per-impl manual generators (Chicken’swiki2html, Guile’stexinfointegration). - Notable users/projects: GNU Guix (package manager + OS, Guile), Lilypond (music typesetter, Guile), GnuCash (Guile-scripted), Bitlbee, TextMate (Scheme-flavored bundles), Naughty Dog (GOAL/GOOL — CL-leaning but Scheme-influenced), Termite (Erlang-style distribution on Gambit), Mongoose (game scripting), Snabb Switch (Lua-with-Scheme tooling), MIT 6.001 / SICP (the canonical CS curriculum, decades).
Gotchas
- Tail calls require exact tail position —
(begin (f x))is tail;(begin (f x) #f)is not (the#ffollows).(if c (f x) (g y))— both arms are tail.(let ((y (f x))) y)—(f x)is not tail (let body has to receive it). '()is true,#fis false — coming from CL wherenilis both, this trips.(if '() 1 2)is1.- Exact vs inexact contagion —
(* 1/3 3.0)is1.0(inexact);(* 1/3 3)is1(exact). Mixing yields the wider, less-exact result. - Implementations diverge wildly past the spec — code that runs on Guile may not run on Chez. Use
cond-expandand target a SRFI/R7RS subset for portability. call/cc+ dynamic-wind semantics —dynamic-windensuresbefore/afterthunks fire on every entry/exit, even via continuation jump. Beginners reinvoke a captured continuation and are surprised when before/after re-runs.eq?/eqv?/equal?—eq?is implementation-defined (often pointer);eqv?adds numbers/chars;equal?is structural.(eq? 1 1)may be#tor#fdepending on impl.- Hygienic macros and pattern-binding name collisions — patterns introduce bindings in the template;
(syntax-rules () ((_ x) (let ((x 1)) x)))introduces a hygienicxdistinct from the pattern’sx. defineplacement — most R7RS impls allow internal defines only at the start of alambda/letbody; mixingdefineand expressions varies.- Reader case — the spec says symbols are case-sensitive (R6RS, R7RS). Some legacy impls fold to lowercase; Chez has both modes.
- Vector vs list vs bytevector literals —
#(1 2 3)vector,'(1 2 3)list,#u8(1 2 3)bytevector. Mixing types in operations errors. - Record types are opaque by default — printing a record yields
#<record>unless you implement custom printing per impl. - No standard package manager / module loader — the most painful portability gap. R7RS
define-libraryis the spec, but the file-search mechanism (where(import (foo bar))looks) is impl-defined. - Continuations are heavy in some impls — Chicken’s Cheney-on-the-MTA makes them O(1) capture but every minor-GC re-scans; implementation-specific perf tradeoffs matter.
Citations
- R7RS-small (Final, 2013): https://small.r7rs.org/ • PDF: https://small.r7rs.org/attachment/r7rs.pdf
- R6RS (2007): http://www.r6rs.org/
- R5RS (1998): https://schemers.org/Documents/Standards/R5RS/
- Scheme.org hub: https://www.scheme.org/
- SRFI index: https://srfi.schemers.org/ • finalized SRFIs: https://srfi.schemers.org/final-srfis.html
- Structure and Interpretation of Computer Programs (free): https://mitp-content-server.mit.edu/books/content/sectbyfn/books_pres_0/6515/sicp.zip/index.html
- How to Design Programs 2/e: https://htdp.org/
- Chez Scheme: https://cisco.github.io/ChezScheme/ • manual: https://cisco.github.io/ChezScheme/csug9.5/csug.html
- Guile: https://www.gnu.org/software/guile/ • manual: https://www.gnu.org/software/guile/manual/
- Chicken: https://www.call-cc.org/ • manual: https://wiki.call-cc.org/man/5/
- Gambit: https://gambitscheme.org/ • Gerbil: https://cons.io/
- Chibi: https://github.com/ashinn/chibi-scheme • MIT/GNU: https://www.gnu.org/software/mit-scheme/
- Cyclone: https://justinethier.github.io/cyclone/
- Nanopass framework: https://nanopass.org/ • Sarkar et al. 2004 paper: https://www.cs.indiana.edu/~dyb/pubs/nano-jfp.pdf
- Cheney-on-the-MTA (Baker): https://www.pipeline.com/~hbaker1/CheneyMTA.html
- Guix: https://guix.gnu.org/
- Andy Wingo’s blog (Guile internals): https://wingolog.org/
- Lambda Papers (Steele/Sussman, 1975+): https://research.scheme.org/lambda-papers/
- Geiser (Emacs IDE): https://www.nongnu.org/geiser/