programming4 min read

Tutorial: Learn Racket from Scratch (2026)

Tutorial: Learn Racket from Scratch (2026)

Published:  |  Category: Programming  |  Reading time: ~15 min
Tutorial: Learn Racket from Scratch (2026)

Racket is a descendant of Scheme and a language laboratory for creating programming languages. I have used it for teaching programming concepts, building DSLs, and prototyping language features. Racket's philosophy is that a language should grow with its user — when you need a new abstraction, you extend the language instead of working around it.

The macro system in Racket is hygienic and operates on syntax objects, making it more predictable than Lisp's traditional macros while retaining their power.

Macros

Racket's macro system is hygienic: it preserves lexical scope, preventing variable capture. define-syntax-rule creates simple pattern-based macros. syntax-rules provides more control. syntax-parse (from syntax/parse) offers the most powerful pattern matching with error messages. Macros can generate arbitrary code at compile time.

(define-syntax-rule (unless cond body ...)
  (when (not cond) body ...))

(unless #f
  (displayln "this runs"))

;; Pattern-based macro
(define-syntax-rule (for/list ([x expr] ...) body ...)
  (map (lambda (x) body ...) expr ...))

(for/list ([x '(1 2 3)]
           [y '(4 5 6)])
  (+ x y))
;; => '(5 7 9)

;; Syntax-parse macro
(require syntax/parse)
(define-syntax (define-logged stx)
  (syntax-parse stx
    [(_ (name args ...) body ...)
     #'(define (name args ...)
         (printf "calling ~a~n" 'name)
         body ...)]))

DSLs

Racket excels at creating domain-specific languages. The #lang mechanism lets you create entirely new languages. Readers transform source at the lexical level. Syntax properties annotate syntax objects with metadata. Racket comes with many built-in languages: typed/racket, lazy, scribble (documentation), and slideshow (presentations).

#lang racket

;; Define a simple DSL
(define-syntax (game stx)
  (syntax-parse stx
    [(_ (state:id (var:id init:expr) ...) cmd ...)
     (with-syntax ([(var ...) (syntax->list #'(var ...))])
       #'(let loop ([state state] [var init] ...)
           (displayln state)
           (match (read)
             cmd ...
             [_ (loop state var ...)])))]))

(game (playing [score 0][lives 3])
  [(? string? s)
   (printf "you said ~a~n" s)
   (loop 'playing score lives)]
  ['quit
   (displayln "game over")])

Contracts

Contracts specify behavioral guarantees between components. contract-out attaches contracts to exports. Contracts can check types, ranges, and even temporal properties. flat-contract checks simple predicates. ->i creates dependent contracts where the output condition depends on the input. Contracts are checked at module boundaries.

(provide
  (contract-out
    [positive? (-> number? boolean?)]
    [divide (->i ([a number?][b (and/c number? (not/c zero?))])
                 [result (a b) (<=? result a)])]
    [counter (-> (case->
                   (-> exact-integer?)
                   (-> void?)))]))

(define (positive? n)
  (> n 0))

(define (divide a b)
  (/ a b))

(define (counter)
  (let ([n 0])
    (case-lambda
      [(inc!) (set! n (+ n 1))]
      [(get) n])))

Scribble

Scribble is Racket's documentation and literate programming system. Document prose using @-expressions, which look like XML tags but are S-expressions. scribble/manual produces HTML or PDF. Scribble can render code, equations, figures, and cross-references. Many Racket package docs are written in Scribble and bundled with the code.

#lang scribble/manual

@title{My Library}

@defmodule[my-lib]

This library provides @emph{useful} utilities.

@defproc[(add [a number?][b number?]) number?]{
  Returns the sum of @racket[a] and @racket[b].
}

@defform[(unless cond body ...)]{
  Evaluates @racket[body] when @racket[cond] is @racket[#f].
}

@examples[
  (add 2 3)
  (unless #f (displayln "yes"))
]

@section{Installation}

@codeblock|{
  raco pkg install my-lib
}|

Class System

Racket has a class system that supports single inheritance, interfaces, mixins, and traits. Classes are first-class values — they can be created at runtime, passed to functions, and modified. Mixins combine behavior from multiple sources. Traits resolve name conflicts. This is useful for GUI programming (classes provide GUI toolkit) and object-oriented design.

(define my-point%
  (class object%
    (super-new)
    (init-field x y)
    (define/public (distance-to-origin)
      (sqrt (+ (* x x) (* y y))))
    (define/public (move dx dy)
      (set! x (+ x dx))
      (set! y (+ y dy)))
    (define/public (get-x) x)
    (define/public (get-y) y))

(define colored-point%
  (class my-point%
    (super-new)
    (init-field color)
    (define/public (draw)
      (displayln (format "draw ~a at (~a,~a)" color (get-x) (get-y)))))

(define p (new colored-point% [x 10] [y 20] [color "red"]))
(send p draw)
(send p move 5 5)

Raco (Package Manager)

Raco is Racket's command-line tool for package management, documentation, testing, and more. raco pkg install installs packages. raco test runs tests. raco docs opens documentation. raco exe creates standalone executables. raco make compiles sources to bytecode for faster loading.

;; Command line examples:
;; raco pkg install xml            # install package
;; raco pkg update --all            # update all packages
;; raco test .                      # run tests
;; raco exe my-app.rkt              # create executable
;; raco demod my-app.zo             # link bytecode (demodularize)
;; raco planet                      # browse packages

;; Package info (info.rkt):
#lang info
(define collection "my-lib")
(define deps '(["base" #:version "7.0"]))
(define build-deps '(["rackunit-lib"]))
(define scribble '(scribblings "my-lib.scrbl"))

Frequently Asked Questions

Racket vs Common Lisp?

Racket is cleaner (hygienic macros, no separate function/var namespace), has a module system, and focuses on language creation. CL has a larger ecosystem and multiple implementations.

What does #lang do?

#lang is the module declaration that specifies the language for the file. Racket can switch languages per file, enabling DSL creation.

What is a syntax object?

A syntax object pairs an S-expression with lexical context (scope, source location). This context enables hygienic macros that avoid variable capture.

What is the difference between define and let?

define creates top-level or local bindings. let creates local bindings with lexical scope. define is allowed only at the module or function top level.

Originally published on Ayodhyyya. Last updated June 1, 2026.