2 Reading Racket
This guide covers just enough Racket to read and write Stone pipelines. It isn’t a Racket tutorial — if you want to learn the language proper, start with Quick: An Introduction to Racket with Pictures or The Racket Guide. Everything here is the subset Stone uses pervasively, nothing more.
2.1 Parentheses, prefix, and lambdas
A Racket expression is a parenthesized list. The first element is the operator; the rest are its arguments.
(+ 1 2) ; => 3 (list 'a 'b 'c) ; => '(a b c) (define x 42) ; binds x to 42
A function is a lambda:
You’ll see this shape constantly — Stone’s ashlar bodies are (lambda (dag) ...). The parameter list is in parens, the body is the rest.
define can name a function directly:
(define (my-ashlar dag) (typed-node dag 'hello (hasheq)))
equivalent to
(define my-ashlar (lambda (dag) (typed-node dag 'hello (hasheq))))
2.2 Keyword arguments
Stone uses keyword arguments heavily. A keyword is an identifier prefixed with #::
(make-ashlar produce-requirement #:produces 'requirement #:name 'seed)
#:produces and #:name are keyword arguments. They can appear in any order and are optional when the function supplies defaults. When you’re reading a constructor call, the positional argument comes first, and the keyword arguments follow in any order.
2.3 Hashes
A hasheq is the most common shape for node content — a hash table that compares keys with eq? (which is what symbols want):
(hasheq 'name "Alice" 'age 30 'roles '(admin user))
You read a field with hash-ref:
(hash-ref some-hash 'name) ; => "Alice" (hash-ref some-hash 'missing #f) ; => #f (default)
Stone’s node-get is a shape-safe version of the same pattern that tolerates missing nodes and non-hash content:
(node-get (dag-nearest-ancestor dag 'requirement) 'text "")
Prefer node-get in ashlar bodies.
2.4 Lists and quoting
A quoted list is data, not a function call:
'(a b c) ; => (a b c) — a list of symbols (list 'a 'b 'c) ; => (a b c) — equivalent
The quote prevents Racket from evaluating the list as a function call. You’ll see quoted symbols constantly — every node type in Stone is a symbol: 'requirement, 'summary, 'project-config.
2.5 Parameters and dynamic extent
A parameter is a piece of state scoped to a dynamic extent. make-parameter creates one, parameterize rebinds it for the body of an expression:
(define current-user (make-parameter #f)) (parameterize ([current-user "alice"]) (some-function)) ; inside, (current-user) returns "alice" (current-user) ; back outside, it's #f again
Stone uses parameters for current-trace-id, current-span-id, and default-model. The key property is that the rebinding applies to every function call below the parameterize, no matter how deep, without any argument threading.
2.6 define-values for multiple returns
A function can return more than one value; define-values binds the whole tuple:
(define-values (result final-dag) (run-pipeline pipeline (make-dag)))
run-pipeline returns two values — the final node and the updated DAG. define-values names them both.
2.7 Modules and requires
A Racket source file starts with #lang racket to declare the language, then require forms to pull in modules:
(require stone/edge stone/dag stone/llm-ashlar stone/llm-client)
stone/edge makes make-ashlar, ~>, ashlar-loop and friends available. Each Stone module maps to a source file in the package; the reference documents them module by module.
2.8 Threads and channels
The last piece you need for make-ask-human frontends. thread starts a background thread; channels rendezvous between threads.
(define ch (make-channel)) (thread (lambda () (define msg (channel-get ch)) (displayln msg))) (channel-put ch "hello")
channel-put blocks until a reader is ready; channel-get blocks until a writer is ready. An ask-human frontend runs in a thread that channel-gets questions from the ashlar and channel-puts answers back.
sync and handle-evt let a single thread wait on multiple channels at once — the usual shape for a frontend serving several ask-human ashlars.
2.9 Tips for reading nested parentheses
Stone pipelines nest several layers deep. Two reading habits help:
Indentation is load-bearing. Racket code is formatted so that logically grouped arguments align. If you’re lost, look at the column a form starts in — its closing paren is at the same column or on a line that lines up under it.
Read outside in. The outermost form tells you what kind of thing you’re looking at. Start there, then drill into the arguments in order. A (ashlar-loop body #:until pred #:max 5) is always a loop; the details inside are just the body and the parameters.
A Racket-aware editor (DrRacket, Emacs racket-mode, VS Code’s Magic Racket) matches parens and highlights structure. Use one.
2.10 Where to go for more
The Racket Guide — the canonical tutorial.
The Racket Reference — the language itself, comprehensive.
Quick: Introduction with Pictures — a gentle 30-minute tour.
If you can read a function definition, apply it to arguments, destructure a hash, and understand that parameterize changes a value for a scope, you can read any Stone pipeline in this manual.