Skip to the content.

Racket Cheatsheet

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.

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:

;; '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.

;; 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).

;; 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
(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

(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? - 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”).

(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:

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:

  1. Variables introduced by the macro do not capture variables from the surrounding code.
  2. 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.

(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!"