Racket Cheatsheet
- Table of Contents
S-Expressions
S-expressions (symbolic expressions) are the fundamental building blocks of Racket code and data. An S-expression is either an atom or a list.
- Atoms: Basic values like numbers, symbols, booleans, strings, and characters.
- Lists: A sequence of S-expressions enclosed in parentheses
( ... ).
Examples:
;; Atoms
42 ; Number
'foo ; Symbol
#t ; Boolean
"hello" ; String
#\c ; Character
;; Data Structures
'(1 . 2) ; Pair (dotted pair)
#(1 2 3) ; Vector
;; Lists
(+ 1 2) ; A list representing a function call
'(1 2 3) ; A list of numbers (quoted data)
(list 1 2 3) ; A function call constructing a list
(= x (+ y (* 3 x ) z )) ; A nested S-expression
Homoiconicity
Racket is homoiconic, meaning “same representation”. The code structure (S-expressions) is the same as the data structure. This allows code to be treated as data and manipulated programmatically (macros).
;; Code as Data
(define code-snippet '(+ 1 2)) ; A list of data: (+ 1 2)
(eval code-snippet) ; Evaluated as code: 3
;; Modifying Code
(define modified-snippet (cons '* (cdr code-snippet))) ; Change + to *
(eval modified-snippet) ; Evaluated as code: (* 1 2) -> 2
Syntactic Forms
Syntactic forms (or special forms) are expressions that have special evaluation rules, unlike function calls where all arguments are evaluated first.
Common syntactic forms:
define: Binds a value to a name.lambda: Creates a procedure.if: Conditionally evaluates expressions (short-circuiting).quote(or'): Prevents evaluation of its argument.let,let*,letrec: Local bindings.cond,and,or: Conditional control flow.begin: Sequences multiple expressions.set!: Mutates an existing variable.
;; 'if' is a syntactic form because it only evaluates one branch
(if #t 1 (/ 1 0)) ; Returns 1, division by zero is never evaluated
;; 'define' binds a value or defines a function
(define x 10)
(define (square x) (* x x))
;; 'set!' changes the value of an existing variable
(set! x 20) ; x is now 20
;; 'begin' evaluates expressions in order and returns the last result
(begin
(display "First\n")
(display "Second\n")
(+ 1 2)) ; Returns 3
Conditionals
cond
cond allows for multi-way branching. It evaluates a series of conditions and executes the body of the first true condition.
(define (classify-number n)
(cond
[(> n 0) 'positive]
[(< n 0) 'negative]
[else 'zero]))
(classify-number 5) ; 'positive
(classify-number -2) ; 'negative
case
case matches a value against a set of specific constants (using eqv?). It is useful when comparing a value against multiple options.
(define (classify-value v)
(case v
[(1 2 3) 'small]
[(4 5 6) 'medium]
[else 'large]))
(classify-value 2) ; 'small
(classify-value 5) ; 'medium
Quoting
Quoting prevents evaluation of an expression, treating it as data (symbols or lists) instead of code.
quoteor': Standard quoting.quasiquoteor`: Allows unquoting parts of the expression.unquoteor,: Evaluates the expression inside a quasiquote.unquote-splicingor,@: Evaluates and splices the list into the surrounding list.
;; Quote
'(+ 1 2) ; Evaluates to the list (+ 1 2), not 3
(quote (+ 1 2)) ; Same as above
;; Quasiquote and Unquote
(define x 10)
`(+ x ,x) ; Evaluates to the list (+ x 10)
`(+ x ,(+ x 5)) ; Evaluates to the list (+ x 15)
;; Unquote Splicing
(define lst '(1 2 3))
`(0 ,@lst 4) ; Evaluates to (0 1 2 3 4)
`(0 ,lst 4) ; Evaluates to (0 (1 2 3) 4)
Lambdas
Lambdas are anonymous functions.
(lambda (x y) ; this is a comment
(+ (* x x) (* y y)))
Local Bindings (let, let*, letrec)
let
let allows you to bind variables locally. Bindings are evaluated in parallel (variables in the same let cannot refer to each other).
(let ([x 2]
[y 3])
(+ x y)) ; Returns 5
let*
let* evaluates bindings sequentially. Later bindings can use earlier ones.
(let* ([x 2]
[y (+ x 1)])
(+ x y)) ; Returns 5
letrec
letrec allows recursive bindings (bindings can refer to each other), useful for defining recursive local functions.
(letrec ([is-even? (lambda (n)
(if (zero? n)
#t
(is-odd? (- n 1))))]
[is-odd? (lambda (n)
(if (zero? n)
#f
(is-even? (- n 1))))])
(is-even? 10)) ; Returns #t
Named let
let can be given a name to create a recursive loop.
(let loop ([n 10])
(if (zero? n)
'done
(begin
(display n)
(loop (- n 1)))))
while (macro)
Racket does not have a built-in while loop like C or Java, but it can be defined as a macro or simulated with recursion (like named let).
;; Defining a simple while macro
(define-syntax-rule (while condition body ...)
(let loop ()
(when condition
body ...
(loop))))
(define x 5)
(while (> x 0)
(display x)
(set! x (- x 1))) ; Prints 54321
Scoping
Racket uses lexical scoping (static scoping) by default. This means a function’s environment is determined by where it is defined, not where it is called.
;; Lexical Scoping (Default)
(define x 10)
(define (f) x)
(let ([x 20])
(f)) ; Returns 10 (uses global x from definition site)
Closures
A closure is a function that “closes over” the environment in which it was defined. It remembers the values of variables visible at definition time, even if those variables are no longer in scope when the function is called.
(define (make-adder x)
(lambda (y) (+ x y))) ; Returns a closure that remembers 'x'
(define add5 (make-adder 5))
(add5 10) ; 15
;; Stateful Closure (using set!)
(define (iter-vector vec)
(let ([cur 0]
[top (vector-length vec)])
(lambda ()
(if (= cur top)
'<<end>>
(let ([v (vector-ref vec cur)])
(set! cur (+ cur 1))
v)))))
(define i (iter-vector #(1 2)))
(i) ; 1
(i) ; 2
(i) ; '<<end>>
Tail Recursion
Racket optimizes tail calls. If the last expression in a function is a call to another function (or itself), the current stack frame is reused. This prevents stack overflow in recursive loops.
;; Tail Recursive Factorial
(define (factorial n acc)
(if (zero? n)
acc
(factorial (- n 1) (* n acc)))) ; Tail call: result is directly returned
(factorial 5 1) ; 120
;; Non-Tail Recursive Factorial (causes stack buildup)
(define (factorial-bad n)
(if (zero? n)
1
(* n (factorial-bad (- n 1))))) ; Multiplication happens AFTER the recursive call
Pairs and Lists
Lists in Racket are linked lists built from pairs (cons cells).
cons: Constructs a pair.car(orfirst): Gets the first element (head).cdr(orrest): Gets the second element (tail).list: Constructs a list.null?(orempty?): Checks for an empty list.member: Checks if an element is in a list (returns the tail starting with the element or #f).apply: Applies a function to a list of arguments.for-each: Applies a function to each element of a list (for side effects).
;; Pairs
(cons 1 2) ; '(1 . 2) - A dotted pair
(car (cons 1 2)) ; 1
(cdr (cons 1 2)) ; 2
;; Lists (proper lists end with null)
(cons 1 (cons 2 '())) ; '(1 2)
(list 1 2 3) ; '(1 2 3)
;; List Operations
(first '(1 2 3)) ; 1
(rest '(1 2 3)) ; '(2 3)
(car '(1 2 3)) ; 1
(cdr '(1 2 3)) ; '(2 3)
(null? '()) ; #t
(member 2 '(1 2 3)) ; '(2 3)
(member 4 '(1 2 3)) ; #f
;; Apply
(apply + '(1 2 3)) ; 6 (equivalent to (+ 1 2 3))
;; for-each
(for-each (lambda (x) (display x)) '(1 2 3)) ; Prints 123
Higher-Order Functions
Higher-order functions take other functions as arguments or return them. They are essential for processing lists.
map: Applies a function to each element of a list and returns a new list of results.filter: Returns a new list containing only elements for which the predicate returns true.foldl(fold left): Accumulates a result by applying a function from left to right.foldr(fold right): Accumulates a result by applying a function from right to left.
;; Map
(map (lambda (x) (* x x)) '(1 2 3)) ; '(1 4 9)
(map + '(1 2) '(10 20)) ; '(11 22) - map can take multiple lists
;; Filter
(filter even? '(1 2 3 4)) ; '(2 4)
;; Foldl (Left to Right)
;; Accumulates: (f current acc)
(foldl cons '() '(1 2 3)) ; '(3 2 1) - Reverses the list
(foldl - 0 '(10 2)) ; -8 because (- 2 (- 10 0)) -> (- 2 10) -> -8
;; Foldr (Right to Left)
;; Accumulates: (f current acc)
(foldr cons '() '(1 2 3)) ; '(1 2 3) - Preserves order
(foldr - 0 '(10 2)) ; 8 because (- 10 (- 2 0)) -> (- 10 2) -> 8
Vectors
vector: Constructs a vector.vector-ref: Gets an element.vector-set!: Sets an element.vector-for-each: Applies a function to each element of a vector (for side effects).
(define v (vector 10 20 30))
(vector-ref v 1) ; 20
;; vector-for-each
(vector-for-each (lambda (x) (display x)) #(1 2 3)) ; Prints 123
Equivalence Predicates
eq?: Pointer equality. Checks if two objects refer to the exact same memory location. Fast, but fails for numbers or strings that look the same but are stored differently.eqv?: Operational equivalence. Likeeq?, but reliably compares numbers and characters.equal?: Structural equality. Recursively checks if the contents of lists, vectors, strings, etc., are the same.
;; eq? - Identity
(define x '(1 2))
(define y '(1 2))
(eq? x y) ; #f (different lists in memory)
(eq? x x) ; #t
(eq? 'a 'a) ; #t (symbols are unique/interned)
;; eqv? - Numbers and Characters
(eq? 1.0 1.0) ; #f (unspecified behavior for some numbers)
(eqv? 1.0 1.0) ; #t (reliably compares numbers)
;; equal? - Structure
(equal? x y) ; #t (content is the same)
(equal? "hello" "hello") ; #t (strings with same chars)
(eq? "hello" "hello") ; #f (different string objects)
Mutability
Racket values are immutable by default (e.g., lists, symbols), but Racket provides mutable data structures and variables.
Mutable Variables
set! is used to mutate the value of a variable defined with define or bound in a let.
(define x 10)
(set! x 20) ; x is now 20
Mutable Vectors
Vectors are fixed-length mutable arrays.
(define v (vector 1 2 3))
(vector-set! v 0 99) ; v is now #(99 2 3)
Call by Object Sharing
Racket uses call by object sharing (also known as “call by sharing”).
- Arguments are passed by value, but that value is a reference to the object.
- If you mutate a mutable object (like a vector) passed as an argument, the change is visible outside the function.
- If you reassign a variable (using
set!) inside the function, it only changes the local binding and does not affect the variable passed by the caller.
(define (modify-vector v)
(vector-set! v 0 99)) ; Mutation: Visible outside
(define (reassign-vector v)
(set! v (vector 4 5 6))) ; Reassignment: Local only
(define vec (vector 1 2 3))
(modify-vector vec)
vec ; #(99 2 3) - The object was modified
(reassign-vector vec)
vec ; #(99 2 3) - The variable 'vec' still points to the same object
Structs
Structs create new data types with named fields.
Defining a struct (struct name (field1 ...)) automatically creates:
- A constructor:
name - A type predicate:
name? - Field accessors:
name-field1,name-field2…
By default, structs are immutable. Use #:mutable to allow field modification. This creates mutators like set-name-field!.
;; Define a mutable struct
(struct point (x y) #:mutable)
(define p (point 3 4))
(point? p) ; #t
(point-x p) ; 3
;; Mutate fields
(set-point-x! p 10)
(point-x p) ; 10
(set-point-y! p 20)
(point-y p) ; 20
Macros
Macros allow you to extend the language syntax. They are functions that run at compile time, transforming code (S-expressions) into other code before the program runs.
Racket has a powerful macro system. define-syntax-rule is the simplest way to define a macro using pattern matching.
;; Define a macro 'infix' that allows (1 + 2) syntax
(define-syntax-rule (infix a op b)
(op a b))
(infix 1 + 2) ; Becomes (+ 1 2) -> 3
Hygiene
Racket macros are hygienic by default. This means:
- Variables introduced by the macro do not capture variables from the surrounding code.
- Variables used in the macro definition refer to the bindings present where the macro was defined, not where it is used.
This prevents accidental name collisions.
(define-syntax-rule (swap x y)
(let ([tmp x])
(set! x y)
(set! y tmp)))
(let ([tmp 5]
[other 6])
(swap tmp other) ; "tmp" inside swap is renamed to avoid collision with "tmp" here
(list tmp other)) ; '(6 5)
Continuations
A continuation represents the “rest of the computation” at a specific point in time. Racket allows you to capture the current continuation as a first-class function using call-with-current-continuation (or call/cc).
Calling the captured continuation with a value immediately aborts the current computation and returns that value to the point where call/cc was called.
;; Early exit using call/cc
(call/cc
(lambda (k)
(+ 1 2 (k 10) 3))) ; Returns 10 immediately, ignoring (+ 1 2 ... 3)
;; Using call/cc for nonlocal return
(define (search lst target)
(call/cc
(lambda (return)
(for-each (lambda (x)
(if (equal? x target)
(return x) ; Found: return immediately
(display "checking...")))
lst)
#f))) ; Not found
Exception Handling (to check in the future because I don’t know if this is important)
Racket handles exceptions using with-handlers, which catches exceptions raised within its body.
raise: Raises any value as an exception.error: Raises an error with a message.with-handlers: Defines handlers for specific exception types.
(define (safe-divide x y)
(with-handlers ([exn:fail:contract:divide-by-zero?
(lambda (e) 'error-division-by-zero)])
(/ x y)))
(safe-divide 10 2) ; 5
(safe-divide 10 0) ; 'error-division-by-zero
;; Raising custom exceptions
(with-handlers ([string? (lambda (s) (string-append "Caught: " s))])
(raise "Something went wrong!")) ; "Caught: Something went wrong!"