#\a ; Letter 'a'
#\Z ; Letter 'Z'
#\0 ; Digit '0'
#\space ; Space character
#\newline ; Newline character
#\tab ; Tab character
Symbols:
'foo ; Symbol foo
'hello-world ; Symbol hello-world
'+ ; Symbol +
'lambda ; Symbol lambda
; Symbols are like unique identifiers/names
Variables and Definitions
define: Create global variables and functions.
Syntax for variables:
(define variable-name value)
Examples:
; Define a number
(define x 10)
x ; Returns: 10
; Define a string
(define message "Hello")
message ; Returns: "Hello"
; Define using an expression
(define y (* 5 4))
y ; Returns: 20
; Redefine (changes value)
(define x 100)
x ; Returns: 100
; Anonymous function
(lambda (x) (* x x))
; Call immediately
((lambda (x) (* x x)) 5) ; Returns: 25
; Assign to variable
(define square (lambda (x) (* x x)))
(square 5) ; Returns: 25
; Multiple parameters
(define add (lambda (a b) (+ a b)))
(add 3 7) ; Returns: 10
Note: These are equivalent:
; Using lambda explicitly
(define square (lambda (x) (* x x)))
; Shorthand (what we've been using)
(define (square x) (* x x))
; Both create the same function
; Simple let
(let ((x 5)
(y 10))
(+ x y)) ; Returns: 15
; Variables are parallel (can't reference each other)
(let ((x 5)
(y x)) ; Error! x not yet defined
(+ x y))
; Correct way
(let ((x 5))
(let ((y x))
(+ x y))) ; Returns: 10
let*: Create local variables (sequential binding).
(let* ((var1 value1)
(var2 value2) ; Can use var1 here
...)
body-expression)
let* examples:
; Sequential binding
(let* ((x 5)
(y (* x 2))) ; Can use x
(+ x y)) ; Returns: 15
; Each binding can use previous ones
(let* ((a 1)
(b (+ a 1)) ; Uses a
(c (+ a b))) ; Uses a and b
c) ; Returns: 3
; read - read S-expression
(define input (read))
; User types: (1 2 3)
; input is: '(1 2 3)
; read-line - read line as string
(define line (read-line))
; User types: Hello World
; line is: "Hello World"
File I/O:
; Open file for reading
(define in (open-input-file "input.txt"))
; Read from file
(read in)
(read-line in)
; Close file
(close-input-port in)
; Open file for writing
(define out (open-output-file "output.txt"))
; Write to file
(write '(1 2 3) out)
(display "Hello" out)
; Close file
(close-output-port out)
Common Predicates
Predicates are functions that return boolean values.
Predicate
Tests for
Example
null?
Empty list
(null? '()) → #t
pair?
Pair/cons cell
(pair? '(1 2)) → #t
list?
Proper list
(list? '(1 2)) → #t
number?
Number
(number? 42) → #t
integer?
Integer
(integer? 5) → #t
real?
Real number
(real? 3.14) → #t
string?
String
(string? "hi") → #t
char?
Character
(char? #\a) → #t
symbol?
Symbol
(symbol? 'x) → #t
boolean?
Boolean
(boolean? #t) → #t
procedure?
Function
(procedure? +) → #t
zero?
Zero
(zero? 0) → #t
positive?
Positive number
(positive? 5) → #t
negative?
Negative number
(negative? -5) → #t
even?
Even number
(even? 4) → #t
odd?
Odd number
(odd? 3) → #t
Quick Reference Summary
Basic syntax patterns:
; Define variable
(define name value)
; Define function
(define (name params...) body)
; Lambda
(lambda (params...) body)
; Conditional
(if test then else)
(cond (test1 result1) (test2 result2) ... (else default))
; Local binding
(let ((var val) ...) body)
(let* ((var val) ...) body)
(letrec ((var val) ...) body)
; List operations
(car list) ; First element
(cdr list) ; Rest
(cons item list) ; Add to front
(append list1 list2) ; Combine
; Higher-order
(map function list)
(filter predicate list)
(apply function list)
Multiple Expressions in Conditionals
The Problem with if
if only accepts single expressions for then and else branches.
This doesn't work:
; ✗ Error - trying multiple expressions
(if (> x 0)
(display "Positive")
(newline) ; Error! if expects only one expression
(display "Negative"))
You need a way to group multiple expressions into one.
Solution: Using begin
begin: Groups multiple expressions into a single expression.
Chez Scheme provides when and unless for common cases.
when: Execute multiple expressions if condition is true.
; Syntax
(when condition
expression1
expression2
...)
; Equivalent to:
(if condition
(begin expression1 expression2 ...)
(void)) ; or returns unspecified value
when example:
(define x 10)
(when (> x 0)
(display "x is positive")
(newline)
(display "x = ")
(display x))
; No else branch needed
; If condition is false, does nothing
unless: Execute multiple expressions if condition is false.
(define (validate-age age)
(if (and (>= age 0) (<= age 150))
(begin
(display "Age is valid")
(newline)
#t)
(begin
(display "Error: Invalid age")
(newline)
(display "Age must be between 0 and 150")
(newline)
#f)))
Pattern 2: Side effects only when condition is true
; Using when
(when (file-exists? "config.txt")
(display "Loading config...")
(newline)
(load-config)
(display "Config loaded"))
; Without when (more verbose)
(if (file-exists? "config.txt")
(begin
(display "Loading config...")
(newline)
(load-config)
(display "Config loaded"))
(void))
Pattern 3: Guard clauses
(define (process-data data)
; Early return with multiple expressions
(unless (valid-data? data)
(display "Error: Invalid data")
(newline)
(return-error))
; Continue processing if valid
(display "Processing...")
(transform-data data))
Pattern 4: Logging with computation
(define (calculate x y)
(if (= y 0)
(begin
(display "Error: Division by zero")
(newline)
(display "Returning default value")
(newline)
0)
(begin
(display "Calculating: ")
(display x)
(display " / ")
(display y)
(newline)
(/ x y))))
When begin is NOT Needed
Some forms already allow multiple expressions implicitly.
Function bodies (define):
; begin NOT needed in function body
(define (greet name)
(display "Hello, ")
(display name)
(display "!")
(newline)
'done) ; Returns 'done
; All expressions execute in sequence
; Last one is the return value
let, let*, letrec bodies:
; begin NOT needed in let body
(let ((x 10)
(y 20))
(display "Computing sum")
(newline)
(+ x y)) ; Returns 30
lambda bodies:
; begin NOT needed in lambda body
(lambda (x)
(display "Processing: ")
(display x)
(newline)
(* x x)) ; Returns x squared
cond clauses:
; begin NOT needed in cond clauses
(cond
((> x 0)
(display "Positive")
(newline)
'pos)
(else
(display "Not positive")
'not-pos))
case clauses:
; begin NOT needed in case clauses
(case n
((1)
(display "One")
1)
((2)
(display "Two")
2))
Summary: When to Use What
Situation
Use
Example
Multiple expressions in if then/else
begin
(if test (begin e1 e2) e3)
Only execute if true, no else
when
(when test e1 e2)
Only execute if false, no else
unless
(unless test e1 e2)
Multi-way with multiple expressions
cond
(cond (t1 e1 e2) (t2 e3 e4))
In function/lambda/let body
Nothing (implicit)
(define (f) e1 e2)
Quick reference:
; if with multiple expressions
(if test
(begin e1 e2 e3)
(begin e4 e5 e6))
; when (cleaner for one-sided if)
(when test
e1
e2
e3)
; unless (cleaner for negative one-sided if)
(unless test
e1
e2
e3)
; cond (no begin needed)
(cond
(test1 e1 e2 e3)
(test2 e4 e5 e6)
(else e7 e8 e9))
; Function body (no begin needed)
(define (func)
e1
e2
e3)
Chez Scheme Naming Conventions
Understanding Scheme Identifiers
Scheme has flexible rules for naming variables and functions.
Core principle: Names should be descriptive and follow consistent patterns.
Valid characters in identifiers:
Letters: a-z, A-Z
Digits: 0-9 (not at the start)
Special characters: !$%&*+-./:<=>@^_~
Examples of valid identifiers:
; All valid
x
my-variable
list->vector
string-ref
empty?
set!
<=
+
make-counter
file-exists?
*global-config*
Examples of invalid identifiers:
; Invalid - starts with digit
123abc
; Invalid - contains space
my variable
; Invalid - contains special chars not allowed
my#var
item[0]
value{x}
Predicate Naming Convention
Functions that return boolean values end with
Purpose: Immediately identifies a function as a test or check.
; Chez Scheme provides multiple entry points:
; - scheme: Full compiler and interpreter
; - petite: Interpreter-only (no compiler)
; - scheme-script: Script execution mode
; Each mode has different capabilities and use cases
Starting the REPL:
# Start interactive session (full Chez)
$ scheme
Chez Scheme Version 10.0.0
Copyright 1984-2024 Cisco Systems, Inc.
>
# Start without greeting
$ scheme -q
>
# Start interpreter-only version
$ petite
Petite Chez Scheme Version 10.0.0
Copyright 1984-2024 Cisco Systems, Inc.
>
# Display version and exit
$ scheme --version
10.0.0
# Display help
$ scheme --help
Basic REPL interaction:
; Arithmetic
> (+ 2 3)
5
> (* 7 8)
56
> (/ 22 7)
22/7
; String operations
> (string-append "Hello, " "World!")
"Hello, World!"
; List operations
> (cons 1 '(2 3))
(1 2 3)
> (car '(a b c))
a
; Define variables
> (define x 10)
> x
10
> (define name "Alice")
> name
"Alice"
; Define procedures
> (define (square x)
(* x x))
> (square 5)
25
; Lambda expressions
> ((lambda (x y) (+ x y)) 3 4)
7
Multi-line input:
; REPL automatically handles incomplete expressions
> (define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
; Prompt waits for closing parentheses
> (let ([x 10]
[y 20])
(+ x y))
30
; Nested structures
> (define person
'((name . "John")
(age . 30)
(city . "Boston")))
> (assoc 'age person)
(age . 30)
; Display procedure information
> (describe '+)
+: procedure
(+ number ...)
Returns the sum of the arguments.
; Apropos - search for procedures by name pattern
> (apropos "list")
list
list?
list-ref
list-tail
list-head
list-copy
; ... and more
; Search in specific library
> (apropos "fold" 'rnrs)
fold-left
fold-right
; Help system (if loaded)
> (help 'map)
map: procedure
(map procedure list1 list2 ...)
Applies procedure element-wise to the lists.
; Inspect available bindings
> (environment-bindings (interaction-environment))
; Shows all defined names
; Check if bound
> (top-level-bound? 'car)
#t
> (top-level-bound? 'nonexistent)
#f
System information:
; Version information
> (scheme-version)
"10.0.0"
> (scheme-version-number)
#x0a000000 ; Hex encoded version
; Machine type
> (machine-type)
'ta6le ; Threaded, AMD64, Linux
; Operating system
> (machine-type->os)
"Linux"
; Features available
> (features)
(threads 64-bit little-endian ...)
; Check for specific feature
> (threaded?)
#t
> (windows?)
#f
; Installation directory
> (scheme-directory)
"/usr/lib/csv10.0"
; Current directory
> (current-directory)
"/home/user/project"
; Change directory
> (current-directory "/tmp")
> (current-directory)
"/tmp"
; Environment variables
> (getenv "HOME")
"/home/user"
> (getenv "PATH")
"/usr/local/bin:/usr/bin:/bin"
; Set environment variable
> (putenv "MY_VAR" "value")
> (getenv "MY_VAR")
"value"
Timing and profiling:
; Time a single expression
> (time (+ 2 3))
(time (+ 2 3))
no collections
0.000001s elapsed cpu time
0.000001s elapsed real time
0 bytes allocated
5
; Time a longer computation
> (time (apply + (iota 1000000)))
(time (apply + (iota 1000000)))
5 collections
0.021456s elapsed cpu time
0.021523s elapsed real time
24000016 bytes allocated
499999500000
; Time with multiple iterations
> (define (benchmark proc n)
(time
(do ([i 0 (+ i 1)])
((= i n))
(proc))))
> (benchmark (lambda () (apply + (iota 1000))) 10000)
(time (do ([i 0 (+ i 1)]) ((= i n)) (proc)))
248 collections
0.845123s elapsed cpu time
0.846234s elapsed real time
959997104 bytes allocated
; Measure memory only
> (collect) ; Force garbage collection
> (let ([before (bytes-allocated)])
(make-list 1000000 0)
(- (bytes-allocated) before))
8000016 ; Bytes used
; Garbage collection statistics
> (collect)
> (statistics)
; Displays detailed GC statistics
; The expression editor provides:
; - Line editing with arrow keys
; - Multi-line input handling
; - History navigation (up/down arrows)
; - Tab completion (if enabled)
; - Parenthesis matching
; Check if enabled
> (ee-enabled?)
#t
; Disable expression editor
> (ee-disable)
; Re-enable
> (ee-enable)
; History navigation
; Press Up: previous expression
; Press Down: next expression
; Multi-line editing
; Opening ( waits for matching )
> (define (factorial n) ; Press Enter
(if (= n 0) ; Press Enter
1 ; Press Enter
(* n ; Press Enter
(factorial ; Press Enter
(- n 1))))) ; Completes and evaluates
; Interrupt long-running computation
> (let loop () (loop)) ; Infinite loop
^C ; Ctrl+C interrupts
Exception: interrupted
; Reset REPL state
> (reset)
; Clears REPL state, restarts interaction
Multi-value returns:
; Expressions returning multiple values
> (values 1 2 3)
1
2
3
; REPL prints all values returned
> (let-values ([(a b c) (values 1 2 3)])
(+ a b c))
6
; Multiple values from procedures
> (define (quotient-remainder a b)
(values (quotient a b) (remainder a b)))
> (quotient-remainder 17 5)
3
2
> (call-with-values
(lambda () (quotient-remainder 17 5))
+)
5
; Using multiple values
> (let-values ([(q r) (quotient-remainder 17 5)])
(printf "Quotient: ~a, Remainder: ~a~%" q r))
Quotient: 3, Remainder: 2
REPL visualization of features:
Working with Libraries and Imports
Defining a library:
; File: mylib.sls (Scheme Library Source)
(library (mylib)
(export
square
cube
double
halve
clamp)
(import (chezscheme))
(define (square x)
"Return x squared"
(* x x))
(define (cube x)
"Return x cubed"
(* x x x))
(define (double x)
"Return double of x"
(* 2 x))
(define (halve x)
"Return half of x"
(/ x 2))
(define (clamp x min max)
"Clamp x between min and max"
(cond
[(< x min) min]
[(> x max) max]
[else x])))
; File: lib/mathlib/advanced.sls
(library (mathlib advanced)
(export
factorial
fibonacci
gcd)
(import (chezscheme)
(mathlib core)) ; Depends on (mathlib core)
(define (factorial n)
(if (zero? n)
1
(* n (factorial (- n 1)))))
(define (fibonacci n)
(if (<= n 1)
n
(+ (fibonacci (- n 1))
(fibonacci (- n 2)))))
(define (gcd a b)
; Uses square from (mathlib core)
(if (zero? b)
a
(gcd b (remainder a b)))))
; Basic error
> (car 42)
Exception: car: 42 is not a pair
; Error structure:
; - Exception: indicates an error occurred
; - car: the procedure that failed
; - "42 is not a pair": explanation
; Type errors
> (+ "hello" 5)
Exception: +: "hello" is not a number
; Undefined variable
> undefined-var
Exception: undefined variable undefined-var
; Arity errors
> (+ 1)
1 ; + accepts any number of args
> (car)
Exception: car: incorrect number of arguments
> (car '(a b) '(c d))
Exception: car: incorrect number of arguments
Stack traces and context:
; Enable better stack traces
> (debug-level 3)
> (generate-procedure-source-information #t)
; Define nested procedures
> (define (a x) (b (* x 2)))
> (define (b x) (c (+ x 1)))
> (define (c x) (car x))
; Call and trigger error
> (a 5)
Exception in car: 11 is not a pair
at c
at b
at a
at top-level
; Shows call chain leading to error
; More detailed example
> (define (process-list lst)
(if (null? lst)
'()
(cons (process-item (car lst))
(process-list (cdr lst)))))
> (define (process-item x)
(* x x))
> (process-list '(1 2 "three" 4))
Exception in *: "three" is not a number
at process-item
at process-list
at top-level
; Stack trace shows:
; 1. Error in *
; 2. Called from process-item
; 3. Called from process-list
; 4. Called from REPL
Exception handling:
; guard for exception handling
> (guard (ex
[(error-object? ex)
(printf "Error: ~a~%" (error-object-message ex))
#f]
[else
(printf "Unknown exception: ~a~%" ex)
#f])
(car 42))
Error: car: 42 is not a pair
#f
; Multiple condition clauses
> (guard (ex
[(assertion-violation? ex)
(display "Assertion violated!\n")
'assertion]
[(undefined-violation? ex)
(display "Undefined variable!\n")
'undefined]
[(error-object? ex)
(printf "General error: ~a~%" (error-object-message ex))
'error]
[else
(printf "Other: ~a~%" ex)
'other])
(car 42))
General error: car: 42 is not a pair
error
; with-exception-handler
> (with-exception-handler
(lambda (ex)
(printf "Handler called: ~a~%" ex)
'handled)
(lambda ()
(car 42)))
Handler called: Exception: car: 42 is not a pair
handled
; Nested handlers
> (with-exception-handler
(lambda (ex)
(printf "Outer handler~%")
(raise ex))
(lambda ()
(with-exception-handler
(lambda (ex)
(printf "Inner handler~%")
(raise ex))
(lambda ()
(car 42)))))
Inner handler
Outer handler
Exception: car: 42 is not a pair
Tracing procedures:
; Define a recursive function
> (define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
; Trace it
> (trace factorial)
; Now calls show entry and exit
> (factorial 5)
|(factorial 5)
| (factorial 4)
| |(factorial 3)
| | (factorial 2)
| | |(factorial 1)
| | | (factorial 0)
| | | 1
| | |1
| | 2
| |6
| 24
|120
120
; Trace shows:
; - Each call with arguments
; - Indentation for depth
; - Return values
; Trace multiple procedures
> (trace factorial fibonacci)
; Untrace
> (untrace factorial)
; Untrace all
> (untrace)
; Check what's traced
> (traced?)
() ; Nothing traced
; Conditional tracing
> (define (traced-fact n)
(when (> n 10)
(trace factorial))
(let ([result (factorial n)])
(untrace factorial)
result))
Break points and inspection:
; Insert breakpoint in code
> (define (compute x y)
(let ([a (* x 2)]
[b (+ y 3)])
(break) ; Stop here
(+ a b)))
; When break is hit
> (compute 5 10)
Break at:
(+ a b)
debug>
; At debug prompt, inspect variables
debug> x
5
debug> y
10
debug> a
10
debug> b
13
; Evaluate expressions
debug> (+ a b)
23
; Continue execution
debug> (continue)
23
> ; Back to normal REPL
; Conditional breakpoints
> (define (process-list lst)
(if (null? lst)
'()
(let ([item (car lst)])
(when (not (number? item))
(break)) ; Break only for non-numbers
(cons (* item 2) (process-list (cdr lst))))))
> (process-list '(1 2 "three" 4))
Break at:
(cons (* item 2) (process-list (cdr lst)))
debug> item
"three"
debug> lst
("three" 4)
Inspector system:
; Enable inspector information
> (generate-inspector-information #t)
; Define a closure
> (define (make-counter)
(let ([count 0])
(lambda ()
(set! count (+ count 1))
count)))
> (define counter (make-counter))
; Inspect the closure
> (inspect counter)
counter is a procedure
closed over: count = 0
; Call and inspect again
> (counter)
1
> (inspect counter)
counter is a procedure
closed over: count = 1
; Inspect data structures
> (define data '((name . "Alice")
(age . 30)
(city . "Boston")))
> (inspect data)
data is a list:
((name . "Alice")
(age . 30)
(city . "Boston"))
; Inspect records
> (define-record-type person
(fields name age city))
> (define p (make-person "Bob" 25 "NYC"))
> (inspect p)
p is a person record:
name: "Bob"
age: 25
city: "NYC"
Pretty-printing for debugging:
; Pretty-print nested structures
> (define complex-data
'(define (long-function x y z)
(let ([a (compute-alpha x)]
[b (compute-beta y)]
[c (compute-gamma z)])
(if (valid? a b c)
(process a b c)
(fallback x y z)))))
> complex-data
; Without pretty-print (hard to read)
(define (long-function x y z) (let ([a ...]) ...))
> (pretty-print complex-data)
; With pretty-print (readable)
(define (long-function x y z)
(let ([a (compute-alpha x)]
[b (compute-beta y)]
[c (compute-gamma z)])
(if (valid? a b c)
(process a b c)
(fallback x y z))))
; Control pretty-printing
> (pretty-maximum-lines)
#f ; Unlimited
> (pretty-maximum-lines 10)
; Truncates after 10 lines
> (pretty-line-length)
80 ; Characters per line
> (pretty-line-length 120)
; Wider lines
; Pretty-print to string
> (let ([str-port (open-output-string)])
(parameterize ([current-output-port str-port])
(pretty-print complex-data))
(get-output-string str-port))
; Returns formatted string
Debugging visualization:
Creating Standalone Executables
Boot file creation:
; Boot files package compiled code and runtime
; They enable standalone deployment
; Create a boot file
(make-boot-file "myapp.boot"
'() ; List of additional boot files
"main.ss") ; Entry point
; This creates myapp.boot containing:
; - Compiled code from main.ss and dependencies
; - Chez runtime system
; - Everything needed to run independently
; With dependencies
(make-boot-file "myapp.boot"
'()
"main.ss"
"utils.so"
"data.so")
; Multiple entry points
(make-boot-file "myapp.boot"
'()
"init.ss"
"main.ss"
"cleanup.ss")
Running boot files:
# Run boot file directly
$ scheme -b myapp.boot
# Run with arguments
$ scheme -b myapp.boot arg1 arg2
# Specify entry point
$ scheme -b myapp.boot --eval '(main)'
# Silent mode
$ scheme -q -b myapp.boot
Creating shell wrapper:
#!/bin/sh
# File: myapp
# Find scheme executable
SCHEME=$(which scheme)
if [ -z "$SCHEME" ]; then
echo "Error: Chez Scheme not found"
exit 1
fi
# Get script directory
DIR="$(cd "$(dirname "$0")" && pwd)"
# Run boot file
exec "$SCHEME" -q -b "$DIR/myapp.boot" "$@"
# Make executable
$ chmod +x myapp
# Now can run like normal program
$ ./myapp
Hello from standalone app!
$ ./myapp arg1 arg2
Args: (arg1 arg2)
Complete application example:
; File: myapp.ss
#!chezscheme
(import (chezscheme))
; Application configuration
(define app-name "MyApp")
(define app-version "1.0.0")
; Main function
(define (main args)
; Print banner
(printf "~a v~a~%" app-name app-version)
(newline)
; Process arguments
(let ([cmd-args (cdr args)]) ; Skip program name
(cond
[(null? cmd-args)
(print-usage)]
[(member "--help" cmd-args)
(print-usage)]
[(member "--version" cmd-args)
(printf "Version: ~a~%" app-version)]
[else
(process-args cmd-args)])))
; Print usage information
(define (print-usage)
(display "Usage: myapp [options] [arguments]\n")
(display "\n")
(display "Options:\n")
(display " --help Show this help message\n")
(display " --version Show version information\n")
(display " --verbose Enable verbose output\n"))
; Process command-line arguments
(define (process-args args)
(let ([verbose (member "--verbose" args)])
(when verbose
(display "Verbose mode enabled\n"))
; Filter out options
(let ([inputs (filter
(lambda (arg)
(not (string-prefix? "--" arg)))
args)])
(if (null? inputs)
(display "No input files specified\n")
(for-each process-file inputs)))))
; Process a single file
(define (process-file filename)
(printf "Processing: ~a~%" filename)
(if (file-exists? filename)
(begin
(printf " Size: ~a bytes~%"
(file-size filename))
(display " OK\n"))
(printf " Error: file not found~%")))
; Helper: check if string starts with prefix
(define (string-prefix? prefix str)
(and (>= (string-length str)
(string-length prefix))
(string=? prefix
(substring str 0 (string-length prefix)))))
; Auto-run main
(main (command-line))
# Execute build
$ chmod +x build.ss
$ ./build.ss
Building MyApp...
Compiling myapp.ss...
Creating boot file...
Creating launcher...
Build complete!
Run with: ./myapp
# Test the application
$ ./myapp --help
MyApp v1.0.0
Usage: myapp [options] [arguments]
Options:
--help Show this help message
--version Show version information
--verbose Enable verbose output
$ ./myapp --verbose file1.txt file2.txt
MyApp v1.0.0
Verbose mode enabled
Processing: file1.txt
Size: 1234 bytes
OK
Processing: file2.txt
Error: file not found
Deployment checklist:
Alternative: Fully static binary:
# For truly standalone binary (no Scheme installation needed)
# Requires Chez Scheme source and static build
# 1. Build Chez with static linking
$ cd ChezScheme
$ ./configure --disable-shared
$ make install
# 2. Create boot file with scheme.boot included
$ echo '(make-boot-file "standalone.boot"
(list (scheme-boot-file))
"myapp.so")' | scheme
# 3. Create custom executable
# This varies by platform and requires C compilation
# See Chez documentation for platform-specific instructions
# Result: single executable with no dependencies
Workflow Best Practices
Interactive development workflow:
; Recommended REPL-driven development process:
; 1. Start REPL with configuration
$ scheme -q
; 2. Load your code
> (load "mycode.ss")
; 3. Test functions interactively
> (my-function test-input)
; 4. Modify code in editor (keep REPL running)
; 5. Reload modified file
> (load "mycode.ss")
; 6. Test again immediately
> (my-function test-input)
; 7. Repeat steps 4-6 rapidly
; Benefits:
; - Fast iteration
; - Immediate feedback
; - No restart overhead
; - Keep state between reloads (if desired)
# Usage
$ make # Build everything
$ make test # Run tests
$ make run # Run application
$ make clean # Clean compiled files
$ make dev # Development build
Common pitfalls to avoid:
; PITFALL 1: Redefining with different signatures
; Don't:
> (define (func x) ...) ; One parameter
> (define (func x y) ...) ; Two parameters
; Old callers still use one parameter!
; Do: Restart REPL after signature changes
; Or use different names for experimentation
; PITFALL 2: Depending on load order side effects
; Don't:
; file1.ss:
(define x 10)
; file2.ss:
(define y (* x 2)) ; Depends on x from file1
; Do: Make dependencies explicit
; file2.ss:
(import (mylib)) ; Explicitly import
(define y (* (get-x) 2))
; PITFALL 3: Forgetting to recompile after changes
; Don't:
$ vim mylib.ss ; Edit source
$ scheme --script test.ss ; Still loads old .so!
; Do: Recompile or delete .so files
$ rm mylib.so
$ scheme --script test.ss
; Or enable auto-compilation:
$ scheme --compile-imported-libraries --script test.ss
; PITFALL 4: Mixing development and production settings
; Don't:
(optimize-level 3)
; ... later ...
(load "debug-utils.ss") ; Needs debug info!
; Do: Set parameters consistently
; Development:
(optimize-level 1)
(debug-level 3)
; Production:
(optimize-level 3)
(debug-level 0)
; PITFALL 5: Not handling exceptions in scripts
; Don't:
#!/usr/bin/env scheme --script
(import (chezscheme))
(process-files (command-line)) ; May fail
; Do: Add error handling
#!/usr/bin/env scheme --script
(import (chezscheme))
(guard (ex
[else
(fprintf (current-error-port)
"Error: ~a~%" ex)
(exit 1)])
(process-files (cdr (command-line))))
Productivity tips:
REPL shortcuts:
Up/Down arrows: command history
Ctrl+R: reverse history search
Ctrl+C: interrupt computation
Ctrl+D: exit REPL
Helper functions: Define utilities in startup file
Chez Scheme is invoked from the command line using scheme (full version) or petite (interpreter-only version).
General syntax:
# Start interactive REPL
$ scheme
# Start Petite Chez Scheme (no compiler)
$ petite
# Load files before entering REPL
$ scheme file1.scm file2.scm
# Run a script
$ scheme --script myscript.scm
# Run an R6RS program
$ scheme --program myprogram.scm
Execution Mode Options
--script file runs file as a Scheme shell script.
--program file runs file as an R6RS top-level program.
-q or --quiet suppresses greeting banner and prompts.
--optimize-level 0|1|2|3 — Set the optimization level (default: 0).
Levels 0, 1, 2: Safe code with full type and bounds checking
Level 3: Unsafe code, may omit checks for better performance
--debug-on-exception — Enter debugger on uncaught exceptions.
Examples:
# Maximum optimization (unsafe, for production)
$ scheme --optimize-level 3 --program myapp.ss
# Debug a script that's crashing
$ scheme --debug-on-exception --script buggy.ss
# Safe optimization for development
$ scheme --optimize-level 2 --program myapp.ss
When --debug-on-exception is active, exceptions enter the inspector:
$ scheme --debug-on-exception
> (car 5)
Exception in car: 5 is not a pair
Type (debug) to enter the debugger.
debug> i ; inspect continuation
debug> ? ; show help
debug> q ; quit debugger
Expression Editor Options
--eedisable — Disable the expression editor (use plain input).
--eehistory off|file — Set history file location or disable history.
The expression editor provides:
Multi-line expression editing with auto-indentation
Emacs-style key bindings
History across sessions (stored in ~/.chezscheme_history)
Tab completion for identifiers
Parenthesis matching and auto-correction
Examples:
# Disable expression editor (useful for piped input)
$ scheme --eedisable
# Use custom history file
$ scheme --eehistory /tmp/my_history
# Disable history saving
$ scheme --eehistory off
# Piping input (editor auto-disabled)
$ echo '(+ 1 2)' | scheme -q
Boot File Options
-b file or --boot file — Load boot code from specified file.
--verbose — Trace the boot file search process.
Boot files contain compiled Scheme code implementing the system. By default, the system searches for boot files based on the executable name.
; Create boot file depending on petite or scheme
(make-boot-file "myapp.boot"
'("petite" "scheme") ; alternatives
"module1.so"
"module2.so")
; Create standalone boot file (includes petite.boot)
(make-boot-file "standalone.boot"
'() ; no dependencies
"petite.boot"
"myapp.so")
Other Options
--version — Print version information and exit.
--help — Print brief help message and exit.
-- — Pass all remaining arguments to Scheme (not processed as options).
--enable-object-counts — Have garbage collector maintain object counts.
--retain-static-relocation — Keep relocation info for compute-size, etc.
Examples:
# Check version
$ scheme --version
Chez Scheme Version 10.0.0
# Pass arguments that look like options to your script
$ scheme --script myapp.ss -- --my-flag --another-flag
# Enable object counting for memory profiling
$ scheme --enable-object-counts
Environment Variables
SCHEMEHEAPDIRS — Colon-separated list of directories to search for boot files.
%v is replaced by the version number
%m is replaced by the machine type
%% is replaced by a literal %
CHEZSCHEMELIBDIRS — Default value for --libdirs if not specified.
CHEZSCHEMELIBEXTS — Default value for --libexts if not specified.
CHEZSCHEME_HISTORY — Location of expression editor history file.
Examples:
# Set boot file search path
export SCHEMEHEAPDIRS="/opt/chez/%v/%m:/usr/lib/csv%v"
# Set default library directories
export CHEZSCHEMELIBDIRS="/home/user/scheme/libs:/usr/local/lib/scheme"
# Set custom history location
export CHEZSCHEME_HISTORY="$HOME/.config/chez_history"
Accessing Command Line Arguments in Scheme
Use (command-line) to get arguments as a list.
The first element is the script/program name, remaining elements are arguments.
Use (command-line-arguments) for just the arguments (Chez-specific).
(library (mylib combined)
(export
; Export from this library
my-func
; Re-export from rnrs lists
(import (rnrs lists)))
(import (rnrs)
(rnrs lists))
(define (my-func x) ...))
R6RS Records
R6RS provides a powerful record system with syntactic and procedural layers.
Syntactic layer (most common):
#!r6rs
(import (rnrs))
; Define a point record
(define-record-type point
(fields x y))
; Create instances
(define p1 (make-point 3 4))
; Access fields
(point-x p1) ; 3
(point-y p1) ; 4
; Type predicate
(point? p1) ; #t
(point? 42) ; #f
Mutable fields:
(define-record-type counter
(fields (mutable value)))
(define c (make-counter 0))
(counter-value c) ; 0
(counter-value-set! c 10)
(counter-value c) ; 10
; In REPL or build script
(compile-imported-libraries #t) ; Auto-compile imports
(compile-library "mylib.sls")
(compile-program "main.ss")
Library search paths:
# Set library directories
$ scheme --libdirs ./src:./lib --program main.ss
# Set via environment
$ export CHEZSCHEMELIBDIRS="./src:./lib"
$ scheme --program main.ss
Portability Tips
R6RS does not guarantee argument evaluation order:
; Don't rely on left-to-right evaluation
(define counter 0)
(define (inc!) (set! counter (+ counter 1)) counter)
; Result is implementation-dependent!
(list (inc!) (inc!) (inc!)) ; Could be (1 2 3) or (3 2 1) etc.
Fixnum range varies by implementation:
; Minimum guaranteed: 24 bits (-8388608 to 8388607)
; Chez Scheme typically: 61 bits on 64-bit systems
; For portable code, use generic arithmetic for large numbers
(+ x y) ; Always safe
(fx+ x y) ; May overflow on small fixnum implementations
Specify endianness explicitly for binary data:
; Don't use (endianness native) for files/network
(bytevector-u32-set! bv 0 value (endianness big)) ; Portable
Use #!r6rs at the start of portable code to ensure strict compliance.
Control Flow in Chez Scheme
Conditional Expressions
if is the fundamental conditional:
; Basic if: (if test consequent alternative)
(if (> 5 3) 'yes 'no) ; yes
; Without alternative (returns unspecified if false)
(if (> 5 3) (display "yes"))
; Nested if
(define (sign n)
(if (> n 0)
'positive
(if (< n 0)
'negative
'zero)))
(sign 5) ; positive
(sign -3) ; negative
(sign 0) ; zero
; if is an expression, returns a value
(define abs-val
(if (< x 0) (- x) x))
cond for multiple conditions:
; (cond [test expr ...] ... [else expr ...])
(define (grade score)
(cond
[(>= score 90) 'A]
[(>= score 80) 'B]
[(>= score 70) 'C]
[(>= score 60) 'D]
[else 'F]))
(grade 85) ; B
(grade 55) ; F
; cond with => (passes test result to procedure)
(cond
[(assq 'b '((a 1) (b 2) (c 3))) => cadr]
[else 'not-found]) ; 2
; Multiple expressions in clause
(cond
[(even? x)
(display "even")
(newline)
'even]
[else 'odd])
; cond without else (returns unspecified if no match)
(cond
[(= x 1) 'one]
[(= x 2) 'two])
case for value matching:
; (case key [(datum ...) expr ...] ... [else expr ...])
(define (day-type day)
(case day
[(saturday sunday) 'weekend]
[(monday tuesday wednesday thursday friday) 'weekday]
[else 'unknown]))
(day-type 'saturday) ; weekend
(day-type 'monday) ; weekday
; case uses eqv? for comparison
(case (car '(a b))
[(a) 'first]
[(b) 'second]
[else 'other]) ; first
; Multiple values in single clause
(define (vowel? c)
(case c
[(a e i o u A E I O U) #t]
[else #f]))
; case with numeric values
(case (string-length s)
[(0) 'empty]
[(1 2 3) 'short]
[(4 5 6 7 8 9 10) 'medium]
[else 'long])
Conditional forms comparison:
when and unless for one-sided conditionals:
; when: execute body if test is true
(when (> x 0)
(display "positive")
(newline))
; unless: execute body if test is false
(unless (null? lst)
(display "list is not empty")
(process lst))
; Equivalent expansions:
; (when test body ...) = (if test (begin body ...))
; (unless test body ...) = (if (not test) (begin body ...))
; Common use cases
(when debug-mode
(printf "Debug: x = ~a~n" x))
(unless (file-exists? filename)
(error 'load "file not found" filename))
Boolean operations for control flow:
; and: returns first false value or last value
(and (> 5 3) (< 2 4) 'ok) ; ok
(and (> 5 3) (< 4 2) 'ok) ; #f
(and) ; #t (identity for and)
; or: returns first true value or last value
(or (> 1 5) (> 3 2) 'fallback) ; #t
(or #f #f 'default) ; default
(or) ; #f (identity for or)
; Short-circuit evaluation (key feature!)
(and (pair? x) (car x)) ; Safe: car only called if pair
(or (hashtable-ref ht key #f)
(compute-default key)) ; compute only if not found
; Using and/or for control flow
(define (safe-car x)
(and (pair? x) (car x))) ; Returns #f or car
(define (get-or-default ht key default)
(or (hashtable-ref ht key #f)
default))
Sequencing
begin evaluates expressions in order:
; Returns the value of the last expression
(begin
(display "one")
(newline)
(display "two")
(newline)
'done) ; displays "one" and "two", returns done
; Implicit begin in lambda, let, define, cond, etc.
(define (greet name)
(display "Hello, ") ; implicit begin in procedure body
(display name)
(newline))
; begin is often needed in conditionals
(if (check-condition)
(begin
(do-something)
(do-another-thing)
result)
alternative)
; cond clauses have implicit begin
(cond
[(positive? x)
(display "positive") ; multiple expressions OK
(newline)
x]
[else 0])
begin0 returns the first value (Chez extension, but still evaluates all the expressions inside it):
; Returns value of FIRST expression (not last)
(begin0
(get-result) ; This value is returned
(cleanup)
(log-completion))
; Useful for cleanup after getting a value
(define (pop! stack-box)
(begin0
(car (unbox stack-box)) ; Return the popped value
(set-box! stack-box (cdr (unbox stack-box)))))
; Compare begin vs begin0
(begin 'a 'b 'c) ; c (last)
(begin0 'a 'b 'c) ; a (first)
; Practical example: read and advance
(define (read-next port)
(begin0
(read port) ; Return what we read
(skip-whitespace port)))
Sequencing visualization:
Iteration with Named let
Named let creates a local recursive procedure:
; (let name ([var init] ...) body)
(let loop ([i 0])
(when (< i 5)
(display i)
(newline)
(loop (+ i 1))))
; Prints 0 1 2 3 4
; Equivalent to:
(letrec ([loop (lambda (i)
(when (< i 5)
(display i)
(newline)
(loop (+ i 1))))])
(loop 0))
; Summing a list
(define (sum lst)
(let loop ([lst lst] [acc 0])
(if (null? lst)
acc
(loop (cdr lst) (+ acc (car lst))))))
(sum '(1 2 3 4 5)) ; 15
; Finding an element
(define (find pred lst)
(let loop ([lst lst])
(cond
[(null? lst) #f]
[(pred (car lst)) (car lst)]
[else (loop (cdr lst))])))
; (do ([<var1> <init1> <step1>] ...)
; (<test> <result ...>)
; <body ...>)
; Simple counting loop
(do ([i 0 (+ i 1)]) ; var init step
[(= i 5) 'done] ; test result
(display i)
(newline))
; Prints 0 1 2 3 4, returns done
; Factorial with do
(define (factorial n)
(do ([i n (- i 1)]
[acc 1 (* acc i)])
[(zero? i) acc]))
(factorial 5) ; 120
; Multiple variables with different steps
(do ([i 0 (+ i 1)]
[j 10 (- j 1)])
[(= i j) (list i j)]
(printf "i=~a j=~a~n" i j))
; Prints pairs until i and j meet at 5
do loop structure visualization:
Building results with do:
; Map using do
(define (my-map f lst)
(do ([lst lst (cdr lst)]
[result '() (cons (f (car lst)) result)])
[(null? lst) (reverse result)]))
(my-map (lambda (x) (* x x)) '(1 2 3 4 5))
; (1 4 9 16 25)
; Collect first n elements
(define (take n lst)
(do ([i 0 (+ i 1)]
[lst lst (cdr lst)]
[acc '() (cons (car lst) acc)])
[(or (= i n) (null? lst))
(reverse acc)]))
(take 3 '(a b c d e)) ; (a b c)
; Variable without step (unchanged through loop)
(define (repeat-string s n)
(do ([s s] ; No step - s stays same
[i 0 (+ i 1)]
[acc "" (string-append acc s)])
[(= i n) acc]))
(repeat-string "ab" 3) ; "ababab"
Higher-Order Iteration
for-each for side effects:
; Apply procedure to each element (for side effects)
(for-each display '(1 2 3 4 5)) ; Prints: 12345
(for-each
(lambda (x) (printf "Item: ~a~n" x))
'(a b c))
; Item: a
; Item: b
; Item: c
; Multiple lists (stops at shortest)
(for-each
(lambda (x y) (printf "~a + ~a = ~a~n" x y (+ x y)))
'(1 2 3)
'(10 20 30))
; 1 + 10 = 11
; 2 + 20 = 22
; 3 + 30 = 33
; for-each returns unspecified value
; Use map if you need results
; Return multiple values
(define (quotient-remainder n d)
(values (quotient n d) (remainder n d)))
(quotient-remainder 17 5) ; 3 and 2 (two values)
; values with any number of results
(values 1 2 3) ; Three values
(values) ; Zero values
(values 'single) ; One value (same as just 'single)
; Built-in procedures returning multiple values
(div-and-mod 17 5) ; 3 and 2
(exact-integer-sqrt 17) ; 4 and 1 (sqrt and remainder)
(partition even? '(1 2 3 4)) ; (2 4) and (1 3)
Receiving multiple values:
; call-with-values: low-level interface
(call-with-values
(lambda () (values 1 2 3))
(lambda (a b c) (+ a b c))) ; 6
(call-with-values
(lambda () (quotient-remainder 17 5))
(lambda (q r) (list 'quotient q 'remainder r)))
; (quotient 3 remainder 2)
; let-values: convenient syntax
(let-values ([(q r) (quotient-remainder 17 5)])
(printf "17 = 5 * ~a + ~a~n" q r))
; 17 = 5 * 3 + 2
; Multiple clauses
(let-values ([(a b) (values 1 2)]
[(x y z) (values 10 20 30)])
(list a b x y z)) ; (1 2 10 20 30)
; let*-values: sequential binding
(let*-values ([(a b) (values 1 2)]
[(c) (+ a b)])
c) ; 3
(call/cc (lambda (k) body)) or (call-with-current-continuation (lambda (k) body)) captures the current continuation:
; call/cc = call-with-current-continuation
; Captures "what to do next" as a procedure
;; Without call/cc - must compute everything
(+ 1 2 (* 3 4)) ; 15
;; With call/cc - can bail out early
(+ 1 2 (call/cc (lambda (escape)
(escape 100) ; Jump out with 100
(* 3 4)))) ; This is never executed
;; Returns: 103 (1 + 2 + 100)
;; Normal flow - continuation not called
(call/cc (lambda (k)
(+ 1 2)))
;; Returns: 3
;; Jump with continuation
(call/cc (lambda (k)
(k 100)
(+ 1 2))) ; Never reached
;; Returns: 100
; Early exit from computation
(define (find-first pred lst)
(call/cc
(lambda (return)
(for-each
(lambda (x)
(when (pred x)
(return x))) ; Jump out immediately
lst)
#f))) ; Default if not found
(find-first even? '(1 3 5 4 7)) ; 4
(find-first even? '(1 3 5 7)) ; #f
; Product with early exit on zero
(define (product lst)
(call/cc
(lambda (return)
(let loop ([lst lst] [acc 1])
(cond
[(null? lst) acc]
[(zero? (car lst)) (return 0)] ; Short-circuit!
[else (loop (cdr lst) (* acc (car lst)))])))))
(product '(1 2 3 0 4 5)) ; 0 (doesn't multiply 4, 5)
Continuation as first-class value:
Saving and reusing continuations:
; Save continuation for later use
(define saved-k #f)
(+ 1 (call/cc
(lambda (k)
(set! saved-k k)
2))) ; 3 (first time)
(saved-k 10) ; 11 (continues from capture point)
(saved-k 100) ; 101 (can call multiple times)
; Continuation remembers entire context
(let ([x 5])
(+ x (call/cc
(lambda (k)
(set! saved-k k)
10)))) ; 15
(saved-k 20) ; 25 (x is still 5 in that context)
;; --- --- --- --- ---
;; The continuation captures everything from the call/cc point all the way to the "end of the computation" - which could be:
;; - The outermost () in a REPL expression
;; - The end of a function body
;; - The end of a program
;; Step 1: Define a variable to save the continuation
(define saved-k #f)
;; Step 2: Execute the expression with call/cc
(* 2 (+ 3 (call/cc (lambda (k)
;; At this point, k is the continuation
;; k represents: "the rest of the computation"
;; k captures: (* 2 (+ 3 ___))
;; In other words, k is like:
;; (lambda (v) (* 2 (+ 3 v)))
;; Save the continuation for later use
(set! saved-k k)
;; Return 10 as the value
;; This 10 fills the "hole" in (* 2 (+ 3 ___))
10))))
;; What happens during execution:
;; 1. call/cc captures the context: (* 2 (+ 3 ___))
;; 2. It creates a continuation k that represents this context
;; 3. The lambda is called with k
;; 4. We save k into saved-k
;; 5. The lambda returns 10
;; 6. The 10 goes into the hole: (* 2 (+ 3 10))
;; 7. Evaluate: (+ 3 10) = 13
;; 8. Evaluate: (* 2 13) = 26
;; Result: 26
;; ============================================
;; Now saved-k holds the continuation
;; ============================================
;; saved-k is essentially this function:
;; (lambda (v)
;; (+ 3 v) ; First, add 3 to the argument
;; (* 2 ...)) ; Then, multiply the result by 2
;; Or more precisely:
;; (lambda (v) (* 2 (+ 3 v)))
;; ============================================
;; Calling the saved continuation later
;; ============================================
;; Call 1: Pass 5 to the continuation
(saved-k 5)
;; What happens:
;; 1. saved-k is called with argument 5
;; 2. It does the captured computation: (* 2 (+ 3 5))
;; 3. Evaluate: (+ 3 5) = 8
;; 4. Evaluate: (* 2 8) = 16
;; Result: 16
;; Call 2: Pass 100 to the continuation
(saved-k 100)
;; What happens:
;; 1. saved-k is called with argument 100
;; 2. It does the captured computation: (* 2 (+ 3 100))
;; 3. Evaluate: (+ 3 100) = 103
;; 4. Evaluate: (* 2 103) = 206
;; Result: 206
;; Call 3: Pass 0 to the continuation
(saved-k 0)
;; What happens:
;; 1. saved-k is called with argument 0
;; 2. It does the captured computation: (* 2 (+ 3 0))
;; 3. Evaluate: (+ 3 0) = 3
;; 4. Evaluate: (* 2 3) = 6
;; Result: 6
;; Call 4: Pass -10 to the continuation
(saved-k -10)
;; What happens:
;; 1. saved-k is called with argument -10
;; 2. It does the captured computation: (* 2 (+ 3 -10))
;; 3. Evaluate: (+ 3 -10) = -7
;; 4. Evaluate: (* 2 -7) = -14
;; Result: -14
;; ============================================
;; Visual representation
;; ============================================
;; Original expression:
;; (* 2 (+ 3 [HOLE]))
;; ^ ^
;; | |
;; | The hole where call/cc sits
;; |
;; The context that gets captured
;; The continuation k captures "what to do with the value":
;; 1. Take the value
;; 2. Add 3 to it
;; 3. Multiply that by 2
;; 4. Return the final result
Implementing control structures with continuations:
The simplest debugging technique — insert print statements:
; printf works like C's printf but uses ~ instead of %
(define (factorial n)
(printf "factorial called with n = ~a~n" n)
(if (zero? n)
1
(* n (factorial (- n 1)))))
(factorial 5)
; factorial called with n = 5
; factorial called with n = 4
; factorial called with n = 3
; factorial called with n = 2
; factorial called with n = 1
; factorial called with n = 0
; 120
(trace-define-syntax my-or
(syntax-rules ()
[(_) #f]
[(_ e) e]
[(_ e1 e2 ...) (let ([t e1]) (if t t (my-or e2 ...)))]))
(my-or #f #f 'found)
; |(my-or (my-or #f #f 'found))
; |(let ((t #f)) (if t t (my-or #f 'found)))
; found
The Debugger
When an error occurs, Chez Scheme suggests entering the debugger:
> (car 5)
Exception in car: 5 is not a pair
Type (debug) to enter the debugger.
>
Enter the debugger with (debug):
> (debug)
debug>
Debugger commands (type to see):
Command
Description
Show available commands
i
Inspect the raise continuation
c
Inspect the condition
s
Display the condition
e
Exit debugger, retain error continuation
r
Reset to REPL
a
Abort Scheme entirely
n
Enter a new café
Typical debugging session:
> (define (my-reverse lst)
(if (null? lst)
'()
(append (my-reverse (cdr lst)) (car lst))))
> (my-reverse '(1 2 3))
Exception in append: 1 is not a proper list
Type (debug) to enter the debugger.
> (debug)
debug> i ; Inspect continuation
# :
debug> sf ; Show stack frames
The Inspector
After typing i in the debugger, you enter the inspector:
debug> i
# :
Inspector commands for continuations:
Command
Description
Show commands for current object type
Show navigation commands
s or show
Show code and free variables
sf
Show stack frames
d or down
Move down to next frame
u or up
Move up to previous frame
r N or ref N
Inspect Nth free variable
code or c
Inspect the procedure code
call
Inspect the pending call
q or quit
Exit inspector
Inspecting stack frames:
# : sf
0: #
1: #
2: #
3: #
4: #
# : s
continuation: #
procedure code: (lambda (lst) (if (null? lst) ... ...))
call code: (append (my-reverse (cdr lst)) (car lst))
free variables:
0. lst: (1)
# : d ; Move to next frame
# : s
free variables:
0. lst: (2 1)
# : r 0 ; Inspect lst
(2 1) :
Directly inspect any object with inspect:
> (define (square x) (* x x))
> (inspect square)
# : ?
show(s) .......... show code and free variables
code(c) .......... inspect the code for the procedure
ref(r) ........... inspect [nth] free variable
length(l) ........ display number of free variables
# : c
(lambda (x) (* x x)) :
# : quit
>
Inspector commands for different object types:
; Lists
> (inspect '(1 2 3))
(1 2 3) : ?
car .............. inspect car of pair
cdr .............. inspect cdr of pair
ref(r) ........... inspect [nth] car
tail ............. inspect [nth] cdr
show(s) .......... show [n] elements
length(l) ........ display list length
(1 2 3) : car
1 :
; Vectors
> (inspect #(a b c))
#(a b c) : ref 1
b :
break> i ; Inspect continuation
# : sf
0: #
1: #
2: #
... many frames ...
# : s
procedure code: (lambda () (infinite-loop))
call code: (infinite-loop)
break> r ; Reset to REPL
>
Finding where a loop is stuck:
(define (buggy-find x lst)
(cond
[(null? lst) #f]
[(equal? x (car lst)) #t]
[else (buggy-find x lst)])) ; Bug: should be (cdr lst)
> (buggy-find 'z '(a b c))
^C
break> i
# : s
free variables:
0. lst: (a b c) ; lst never changes!
1. x: z
Debug Parameters
debug-on-exception — enter debugger automatically:
; Enable automatic debugging
(debug-on-exception #t)
> (car 5)
Exception in car: 5 is not a pair
debug> ; Directly in debugger, no need for (debug)
; From command line
$ scheme --debug-on-exception --script buggy.ss
debug-level — control debug information (0-3):
; Higher levels retain more debug info
(debug-level 3) ; Maximum debug info (default)
(debug-level 0) ; Minimal debug info (faster)
; Check current level
(debug-level) ; Returns current level
generate-inspector-information — control what's saved:
; Disable for production (smaller, faster code)
(parameterize ([generate-inspector-information #f])
(compile-file "production.ss"))
; Enable for development (better debugging)
(parameterize ([generate-inspector-information #t])
(compile-file "development.ss"))
debug-condition — access the last exception:
> (car 5)
Exception in car: 5 is not a pair
Type (debug) to enter the debugger.
> (debug-condition)
#
> (condition-message (debug-condition))
"~s is not a pair"
> (condition-irritants (debug-condition))
(5)
> (time (factorial 10000))
(time (factorial 10000))
no collections
0.023756917s elapsed cpu time
0.023821000s elapsed real time
134696 bytes allocated
#
statistics for detailed info:
> (statistics)
0 collections
no time in collector
0.156250000s elapsed cpu time
1.234567000s elapsed real time
1234567 bytes allocated
From break/debug, n enters a new café for testing:
> (car 5)
Exception in car: 5 is not a pair
Type (debug) to enter the debugger.
> (debug)
debug> n ; New café
>> ; Can test things here
>> (+ 1 2)
3
>> (exit) ; Return to debug
debug> r ; Reset to main REPL
>
Chez Scheme provides two ways to interact with external code: subprocess communication and direct foreign procedure calls.
system runs a shell command and waits for completion:
; Run a command, wait for it to finish
(system "ls -la") ; Returns exit code
; Capture exit status
(let ([status (system "gcc -o myprogram myprogram.c")])
(if (zero? status)
(display "Compilation succeeded\n")
(printf "Compilation failed with code ~a~n" status)))
; On Unix, negative return means signal termination
(system "sleep 100") ; If killed by SIGTERM, returns -15
open-process-ports creates a subprocess with bidirectional communication:
; "exec" causes shell to replace itself with the command
; This reduces subprocess count and gives correct PID
(define-values (in out err pid)
(open-process-ports "exec python3" 'line (native-transcoder)))
Calling Out of Scheme
foreign-procedure creates a Scheme wrapper for a C function:
; Template for double -> double functions
(define (make-math-proc name)
(foreign-procedure name (double) double))
(define sin (make-math-proc "sin"))
(define cos (make-math-proc "cos"))
(define tan (make-math-proc "tan"))
(define log (make-math-proc "log"))
(define exp (make-math-proc "exp"))
Checking if an entry exists:
(foreign-entry? "strlen") ; #t if available
(foreign-entry? "nonexistent_function") ; #f
; Get address of entry
(foreign-entry "strlen") ; Address as exact integer
Calling Into Scheme
foreign-callable creates a C-callable wrapper for a Scheme procedure:
; Syntax: (foreign-callable proc (param-types ...) return-type)
; Create callable wrapper
(define scheme-callback
(foreign-callable
(lambda (x y)
(printf "Scheme received: ~a, ~a~n" x y)
(+ x y))
(int int)
int))
; Lock the code object (prevents garbage collection)
(lock-object scheme-callback)
; Get the entry point address for C code
(define callback-addr
(foreign-callable-entry-point scheme-callback))
; Convert address back to code object and unlock
(define (free-callback addr)
(unlock-object
(foreign-callable-code-object addr)))
; When done with callback
(free-callback on-a-pressed)
Using ftypes for callbacks:
; Define function type
(define-ftype callback-t (function (int int) int))
; Create ftype pointer from Scheme procedure
(define adder-fptr
(make-ftype-pointer callback-t
(lambda (x y) (+ x y))))
; Get address (code is auto-locked)
(ftype-pointer-address adder-fptr)
; Unlock when done
(unlock-object
(foreign-callable-code-object
(ftype-pointer-address adder-fptr)))
Continuations and Foreign Calls
Chez Scheme allows arbitrary nesting of Scheme and C calls, but continuations require care.
A foreign context becomes "stale" after returning through it:
; Scheme calls C calls Scheme calls C...
; Each C frame creates a "foreign context"
; Safe: Non-local exits (throw upward)
(call/cc
(lambda (escape)
(c-function-that-calls-scheme
(lambda ()
(escape 'early-exit))))) ; OK: jumps out
; Dangerous: Returning to stale context
(define saved-k #f)
(c-function-that-calls-scheme
(lambda ()
(call/cc (lambda (k) (set! saved-k k)))))
; Later...
(saved-k 'value) ; BAD: C context is stale!
Resetting C stack with continuations:
; Helper to reset C stack on continuation invocation
(define (with-exit-proc p)
(define th (lambda () (call/cc p)))
(define-ftype ->ptr (function () ptr))
(let ([fptr (make-ftype-pointer ->ptr th)])
(let ([v ((ftype-ref ->ptr () fptr))])
(unlock-object
(foreign-callable-code-object
(ftype-pointer-address fptr)))
v)))
; Usage: like call/cc but resets C stack
(with-exit-proc
(lambda (exit)
(process-items
(lambda (item)
(when (error-item? item)
(exit 'error))))
'success))
General rules:
Non-local exits (escape continuations) are always safe
Don't save continuations captured inside C callbacks for later use
If you must use general continuations, reset C stack first
Foreign Data
foreign-alloc and foreign-free manage foreign memory:
; Allocate 100 bytes
(define ptr (foreign-alloc 100))
; Use the memory...
; Free when done
(foreign-free ptr)
foreign-ref and foreign-set! read/write foreign memory:
; Allocate space for an int
(define int-ptr (foreign-alloc (foreign-sizeof 'int)))
; Write value
(foreign-set! 'int int-ptr 0 42)
; Read value
(foreign-ref 'int int-ptr 0) ; 42
; Different types at different offsets
(define buf (foreign-alloc 24))
(foreign-set! 'integer-32 buf 0 100)
(foreign-set! 'double buf 8 3.14159)
(foreign-set! 'char buf 16 #\A)
(foreign-ref 'integer-32 buf 0) ; 100
(foreign-ref 'double buf 8) ; 3.14159
(foreign-ref 'char buf 16) ; #\A
(foreign-free buf)
; Allocate and create pointer
(define p
(make-ftype-pointer Point
(foreign-alloc (ftype-sizeof Point))))
; Set fields
(ftype-set! Point (x) p 3.0)
(ftype-set! Point (y) p 4.0)
; Get fields
(ftype-ref Point (x) p) ; 3.0
(ftype-ref Point (y) p) ; 4.0
; Get address of field
(ftype-&ref Point (x) p) ; Pointer to x field
; Convert to s-expression for debugging
(ftype-pointer->sexpr p)
; (struct [x 3.0] [y 4.0])
; Free when done
(foreign-free (ftype-pointer-address p))
Complex ftype example:
(define-ftype Person
(struct
[name (array 64 char)]
[age integer-32]
[height double]
[next (* Person)])) ; Linked list
; Allocate
(define person
(make-ftype-pointer Person
(foreign-alloc (ftype-sizeof Person))))
; Set fields
(ftype-set! Person (age) person 30)
(ftype-set! Person (height) person 1.75)
(ftype-set! Person (next) person
(make-ftype-pointer Person 0)) ; null pointer
; Access array elements
(ftype-set! Person (name 0) person #\J)
(ftype-set! Person (name 1) person #\o)
(ftype-set! Person (name 2) person #\e)
(ftype-set! Person (name 3) person #\nul)
Foreign Arrays of Managed Objects
Arrays with pointers to Scheme objects require special care due to garbage collection.
Lock objects that foreign code will reference:
; Create array of Scheme objects for C access
(define (make-object-array objects)
(let* ([n (length objects)]
[ptr (foreign-alloc (* n (foreign-sizeof 'ptr)))])
; Store and lock each object
(let loop ([objs objects] [i 0])
(unless (null? objs)
(let ([obj (car objs)])
(lock-object obj)
(foreign-set! 'ptr ptr (* i (foreign-sizeof 'ptr)) obj))
(loop (cdr objs) (+ i 1))))
ptr))
; Free and unlock
(define (free-object-array ptr n)
(do ([i 0 (+ i 1)])
((= i n))
(let ([obj (foreign-ref 'ptr ptr (* i (foreign-sizeof 'ptr)))])
(unlock-object obj)))
(foreign-free ptr))
Using bytevectors for binary data (automatically managed):
; Pass bytevector to C as u8*
(define bv (make-bytevector 100 0))
; C function receiving bytevector
(define fill-buffer
(foreign-procedure "fill_buffer" (u8* size_t) int))
; Bytevector data is accessible during call
(fill-buffer bv (bytevector-length bv))
; Don't store bv pointer in C across Scheme calls!
Converting between strings and foreign memory:
; String to bytevector (for passing to C)
(define (string->foreign-utf8 str)
(string->utf8 str))
; Bytevector from C (null-terminated)
(define (foreign-utf8->string bv)
(let loop ([i 0])
(if (zero? (bytevector-u8-ref bv i))
(utf8->string (bytevector-copy bv 0 i))
(loop (+ i 1)))))
# Linux
$ gcc -fPIC -shared -o mylib.so mylib.c
# macOS
$ gcc -dynamiclib -o mylib.dylib mylib.c
# Windows (Visual Studio)
$ cl -c mylib.c
$ link -dll -out:mylib.dll mylib.obj
Export symbols on Windows:
// mylib.c for Windows
#ifdef WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
EXPORT int add(int x, int y) {
return x + y;
}
Registering symbols manually:
; Check if entry exists
(foreign-entry? "my_function") ; #t or #f
; Get address
(foreign-entry "my_function") ; Address
; Get name from address
(foreign-address-name addr) ; "my_function" or #f
; Remove entry (for cleanup)
(remove-foreign-entry "my_function")
Using Other Foreign Languages
Any language that follows C calling conventions can interoperate.
Name mangling considerations:
; C compilers may prepend underscore to names
; Use "=" prefix to bypass name interpretation
(foreign-entry? "foo") ; May be false
(foreign-entry? "=foo") ; Check raw name
; For assembly-coded procedures
(foreign-procedure "=_my_asm_proc" (int) int)
// C++ code - use extern "C" to disable name mangling
extern "C" {
int my_function(int x) {
return x * 2;
}
// Create/destroy C++ objects through C interface
void* create_object() {
return new MyClass();
}
void destroy_object(void* ptr) {
delete static_cast<MyClass*>(ptr);
}
int call_method(void* ptr, int arg) {
return static_cast<MyClass*>(ptr)->method(arg);
}
}
Chez Scheme provides C-callable routines via scheme.h:
/* Include Chez Scheme header */
#include "scheme.h"
/* Predicates: check object types */
Sfixnump(obj) /* Is obj a fixnum? */
Spairp(obj) /* Is obj a pair? */
Sstringp(obj) /* Is obj a string? */
Svectorp(obj) /* Is obj a vector? */
Sprocedurep(obj) /* Is obj a procedure? */
/* Accessors: extract values */
Sfixnum_value(fixnum) /* fixnum -> C integer */
Schar_value(char) /* character -> C char */
Sflonum_value(flonum) /* flonum -> C double */
Scar(pair) /* car of pair */
Scdr(pair) /* cdr of pair */
Sstring_length(str) /* string length */
Svector_ref(vec, i) /* vector element */
Constructors:
/* Immediate values */
Snil /* Empty list */
Strue /* #t */
Sfalse /* #f */
Svoid /* void */
/* Create Scheme objects */
Sfixnum(n) /* C int -> fixnum */
Schar(c) /* C char -> character */
Sflonum(x) /* C double -> flonum */
Sstring(s) /* C string -> Scheme string */
Scons(car, cdr) /* Create pair */
Sbox(obj) /* Create box */
Smake_string(n, c) /* Make string of length n */
Smake_vector(n, obj) /* Make vector of length n */
Sinteger(n) /* C int -> exact integer (any size) */
Sstring_to_symbol(s) /* C string -> symbol */
Calling Scheme from C:
/* Call with 0-3 arguments */
ptr Scall0(ptr proc);
ptr Scall1(ptr proc, ptr arg1);
ptr Scall2(ptr proc, ptr arg1, ptr arg2);
ptr Scall3(ptr proc, ptr arg1, ptr arg2, ptr arg3);
/* General call interface */
void Sinitframe(iptr n); /* Initialize for n args */
void Sput_arg(iptr i, ptr arg); /* Set argument i */
ptr Scall(ptr proc, iptr n); /* Call with n args */
/* Example: call Scheme + from C */
ptr plus = Stop_level_value(Sstring_to_symbol("+"));
ptr result = Scall2(plus, Sfixnum(3), Sfixnum(4));
/* result is Scheme 7 */
Locking objects:
/* Prevent GC from moving/collecting object */
void Slock_object(ptr obj);
void Sunlock_object(ptr obj);
/* Must lock Scheme objects that C code retains
across calls back into Scheme */
ptr str = Sstring("hello");
Slock_object(str);
/* ... use str, possibly calling Scheme ... */
Sunlock_object(str);
Registering foreign symbols:
/* Make C function visible to Scheme */
void Sforeign_symbol(const char * name, void * addr);
void Sregister_symbol(const char * name, void * addr);
/* Example */
int my_add(int x, int y) { return x + y; }
void init_my_lib(void) {
Sregister_symbol("my_add", (void*)my_add);
}
Creating a process port (bidirectional communication with subprocess):
;; Create port that wraps socket I/O
(define (make-socket-port sock)
(define (read! bv start count)
(let ([buf (make-string count)])
(let ([n (c-read sock buf count)])
(do ([i 0 (+ i 1)])
((= i n) n)
(bytevector-u8-set! bv (+ start i)
(char->integer (string-ref buf i)))))))
(define (write! bv start count)
(let ([str (make-string count)])
(do ([i 0 (+ i 1)])
((= i count))
(string-set! str i
(integer->char (bytevector-u8-ref bv (+ start i)))))
(c-write sock str count)))
(define (close-proc)
(close sock))
(make-custom-binary-input/output-port
"socket-port"
read!
write!
#f ; get-position
#f ; set-position
close-proc))
Binding Forms in Chez Scheme
Definitions
The define form creates bindings for variables and procedures:
; Variable definition
(define x 42)
(define greeting "Hello, World!")
; Procedure definition (full form)
(define square
(lambda (x)
(* x x)))
; Procedure definition (shorthand)
(define (square x)
(* x x))
; Shorthand with multiple parameters
(define (add a b)
(+ a b))
; Shorthand with rest parameter
(define (sum . numbers)
(apply + numbers))
(sum 1 2 3 4 5) ; 15
; Shorthand with required and rest parameters
(define (format-message prefix . args)
(apply format prefix args))
let creates local bindings evaluated in parallel:
; Basic let
(let ([x 10]
[y 20])
(+ x y)) ; 30
; Bindings are parallel - can't reference each other
(let ([x 5]
[y x]) ; ERROR: x not yet bound
(+ x y))
; Shadowing outer bindings
(define x 100)
(let ([x 1])
x) ; 1 (inner x shadows outer)
x ; 100 (outer x unchanged)
let* creates sequential bindings:
; Bindings are sequential - later can reference earlier
(let* ([x 5]
[y (* x 2)] ; Can use x
[z (+ x y)]) ; Can use x and y
z) ; 15
; Equivalent to nested lets
(let ([x 5])
(let ([y (* x 2)])
(let ([z (+ x y)])
z)))
; Useful for step-by-step computation
(let* ([input " hello world "]
[trimmed (string-trim input)]
[upper (string-upcase trimmed)]
[words (string-split upper " ")])
words) ; ("HELLO" "WORLD")
letrec creates mutually recursive bindings:
; Mutually recursive procedures
(letrec ([even? (lambda (n)
(or (zero? n)
(odd? (- n 1))))]
[odd? (lambda (n)
(and (not (zero? n))
(even? (- n 1))))])
(list (even? 10) (odd? 10))) ; (#t #f)
; Self-recursive procedure
(letrec ([factorial
(lambda (n)
(if (zero? n)
1
(* n (factorial (- n 1)))))])
(factorial 5)) ; 120
; All bindings in scope for all right-hand sides
(letrec ([a (lambda () (b))]
[b (lambda () 42)])
(a)) ; 42
letrec* combines letrec semantics with left-to-right evaluation:
; Guaranteed left-to-right evaluation
(letrec* ([x 1]
[y (+ x 1)] ; Can reference x's value
[z (+ y 1)]) ; Can reference y's value
(list x y z)) ; (1 2 3)
; Useful when initialization order matters
(letrec* ([counter 0]
[inc! (lambda ()
(set! counter (+ counter 1))
counter)]
[first (inc!)] ; Evaluated first
[second (inc!)]) ; Evaluated second
(list first second counter)) ; (1 2 2)
Binding evaluation order visualization:
Named let for recursive iteration:
; Named let creates a recursive procedure
(let factorial ([n 5] [acc 1])
(if (zero? n)
acc
(factorial (- n 1) (* n acc)))) ; 120
; Equivalent to:
(letrec ([factorial (lambda (n acc)
(if (zero? n)
acc
(factorial (- n 1) (* n acc))))])
(factorial 5 1))
; List processing with named let
(let factorial ([lst '(1 2 3 4 5)] [sum 0])
(if (null? lst)
sum
(factorial (cdr lst) (+ sum (car lst))))) ; 15
; Building a result list
(let factorial ([n 5] [result '()])
(if (zero? n)
result
(factorial (- n 1) (cons n result)))) ; (1 2 3 4 5)
Multiple-value Definitions
define-values binds multiple variables from a multiple-value expression:
; Internal defines in procedure body
(define (process-tree tree)
; These are like letrec* bindings
(define (leaf? x) (not (pair? x)))
(define (process-node node)
(if (leaf? node)
(handle-leaf node)
(handle-branch node)))
(define (handle-leaf x) (* x 2))
(define (handle-branch node)
(cons (process-node (car node))
(process-node (cdr node))))
; Body starts here
(process-node tree))
(process-tree '(1 (2 3) 4)) ; (2 (4 6) 8)
; Internal defines can reference later definitions
(define (example)
(define (a) (b)) ; References b defined below
(define (b) 42)
(a))
(example) ; 42
Avoiding evaluation order issues:
; Problematic: referencing value during initialization
(letrec ([x (+ y 1)] ; ERROR: y not yet initialized
[y 5])
x)
; Solution 1: use lambda to delay evaluation
(letrec ([x (lambda () (+ (y) 1))]
[y (lambda () 5)])
((x))) ; 6
; Solution 2: use letrec* with proper order
(letrec* ([y 5]
[x (+ y 1)])
x) ; 6
; Rule of thumb:
; - letrec: for mutually recursive PROCEDURES
; - letrec*: when you need ordered VALUE initialization
; - let/let*: when recursion isn't needed
Fluid Bindings
Parameters provide dynamically-scoped (fluid) bindings:
; Create a parameter with initial value
(define current-user (make-parameter "guest"))
; Access current value
(current-user) ; "guest"
; Set globally
(current-user "admin")
(current-user) ; "admin"
; Dynamic binding with parameterize
(parameterize ([current-user "alice"])
(current-user)) ; "alice"
(current-user) ; "admin" (restored)
; I/O parameters
(current-input-port)
(current-output-port)
(current-error-port)
(parameterize ([current-output-port (open-output-string)])
(display "captured")
(get-output-string (current-output-port))) ; "captured"
; Print parameters
(print-level) ; Max nesting depth to print
(print-length) ; Max list elements to print
(parameterize ([print-length 3])
(printf "~s~n" '(1 2 3 4 5))) ; (1 2 3 ...)
; Compilation parameters
(optimize-level) ; 0-3
(debug-level) ; 0-3
(compile-interpret-simple) ; #t/#f
; Other useful parameters
(interaction-environment) ; Current REPL environment
(source-directories) ; Where to find source files
(library-directories) ; Where to find libraries
Top-Level Bindings
Top-level define creates or modifies bindings:
; Create new binding
(define x 10)
; Redefine (modify) existing binding
(define x 20)
; Top-level defines are mutable
(set! x 30)
; Procedure definitions at top level
(define (greet name)
(printf "Hello, ~a!~n" name))
; Top-level vs local semantics
; At top-level, define can redefine
; In local scope, define creates new binding
top-level-value and set-top-level-value! for programmatic access:
; Get value of top-level binding
(define x 42)
(top-level-value 'x) ; 42
; Set top-level binding
(set-top-level-value! 'y 100)
y ; 100
; Check if bound
(top-level-bound? 'x) ; #t
(top-level-bound? 'nonexistent) ; #f
; Dynamic binding creation
(define (create-binding name value)
(set-top-level-value! name value))
(create-binding 'dynamic-var 999)
dynamic-var ; 999
; Useful for REPL utilities and metaprogramming
(define (show-binding name)
(if (top-level-bound? name)
(printf "~a = ~s~n" name (top-level-value name))
(printf "~a is unbound~n" name)))
define-top-level-value for explicit top-level definition:
; Explicitly define at top level (even from nested context)
(let ([x 10])
(define-top-level-value 'from-let (* x 2)))
from-let ; 20
; Useful in macros that need to create top-level bindings
(define-syntax def-constant
(syntax-rules ()
[(_ name value)
(define-top-level-value 'name value)]))
(def-constant PI 3.14159)
PI ; 3.14159
Interaction environment bindings:
; The interaction environment contains REPL bindings
(interaction-environment) ; Returns the environment
; Evaluate in specific environment
(define env (interaction-environment))
(eval '(define z 50) env)
(eval 'z env) ; 50
; Create new environment (copy of scheme-environment)
(define my-env (copy-environment (scheme-environment)))
(eval '(define private 42) my-env)
; private visible in my-env but not globally
(eval 'private my-env) ; 42
; private ; ERROR: unbound in interaction-environment
; Environment hierarchy
(scheme-environment) ; Built-in bindings
(interaction-environment) ; REPL bindings (includes scheme-environment)
alias creates alternative names for bindings:
; Create an alias (both names refer to same binding)
(define original-name 42)
(alias new-name original-name)
new-name ; 42
(set! new-name 100)
original-name ; 100 (same binding!)
; Alias for imported procedure
(alias λ lambda)
(define add (λ (x y) (+ x y)))
(add 3 4) ; 7
; Alias for keyword
(alias fn define)
(fn (square x) (* x x))
(square 5) ; 25
; Note: alias creates true alias, not copy
; Modifying one affects the other
Top-level syntax definitions:
; Define syntax at top level
(define-syntax when
(syntax-rules ()
[(_ test body ...)
(if test (begin body ...))]))
; Syntax is also a top-level binding
(top-level-bound? 'when) ; #t
; But top-level-value doesn't work for syntax
; (top-level-value 'when) ; ERROR: not a variable
; Check if identifier is bound to syntax
(top-level-syntax? 'when) ; #t
(top-level-syntax? 'car) ; #f (car is a procedure)
; Redefining syntax
(define-syntax my-if
(syntax-rules ()
[(_ test then else)
(if test then else)]))
; Later, can redefine
(define-syntax my-if
(syntax-rules ()
[(_ test then else)
(cond [test then] [else else])]))
Forward references at top level:
; Top-level allows forward references
(define (f x) (g x)) ; References g not yet defined
(define (g x) (* x 2)) ; Now defined
(f 21) ; 42
; This works because:
; 1. define creates a binding location
; 2. The lambda body isn't evaluated until f is called
; 3. By then, g is defined
; Be careful with immediate evaluation
; (define a (+ b 1)) ; ERROR: b not defined
; (define b 10)
; Solution: use thunks or proper ordering
(define b 10)
(define a (+ b 1)) ; OK now
; Or use a thunk for delayed evaluation
(define a-thunk (lambda () (+ b 1)))
(define b 10)
(a-thunk) ; 11
Additional Binding Forms
fluid-let for temporary mutation (deprecated, prefer parameters):
; Local syntax binding
(let-syntax ([swap!
(syntax-rules ()
[(_ a b)
(let ([tmp a])
(set! a b)
(set! b tmp))])])
(define x 1)
(define y 2)
(swap! x y)
(list x y)) ; (2 1)
; swap! not visible outside the let-syntax
; letrec-syntax for mutually recursive macros
(letrec-syntax
([my-or
(syntax-rules ()
[(_) #f]
[(_ e) e]
[(_ e1 e2 ...)
(let ([t e1])
(if t t (my-or e2 ...)))])]
[my-and
(syntax-rules ()
[(_) #t]
[(_ e) e]
[(_ e1 e2 ...)
(if e1 (my-and e2 ...) #f)])])
(my-and (my-or #f 1) (my-or 2 #f))) ; 2
case-lambda for variable arity:
; Different bodies for different arities
(define greet
(case-lambda
[() (greet "World")]
[(name) (printf "Hello, ~a!~n" name)]
[(greeting name) (printf "~a, ~a!~n" greeting name)]))
(greet) ; "Hello, World!"
(greet "Alice") ; "Hello, Alice!"
(greet "Goodbye" "Bob") ; "Goodbye, Bob!"
; With rest arguments
(define sum
(case-lambda
[() 0]
[(x) x]
[(x y) (+ x y)]
[(x y . rest) (apply + x y rest)]))
(sum) ; 0
(sum 1) ; 1
(sum 1 2) ; 3
(sum 1 2 3 4 5) ; 15
; Efficient dispatch based on argument count
(define make-point
(case-lambda
[(x y) (cons x y)]
[(x y z) (vector x y z)]))
receive for multiple values (SRFI-8 style):
; receive binds multiple values more concisely
; (not built-in, but commonly defined)
(define-syntax receive
(syntax-rules ()
[(_ formals expr body ...)
(call-with-values
(lambda () expr)
(lambda formals body ...))]))
(receive (q r) (div-and-mod 17 5)
(printf "~a remainder ~a~n" q r))
; With rest parameter
(receive (first . rest) (values 1 2 3 4)
(list first rest)) ; (1 (2 3 4))
; More concise than let-values for simple cases
(receive (a b c) (values 1 2 3)
(+ a b c)) ; 6
do for iterative binding:
; do combines binding with iteration
(do ([i 0 (+ i 1)] ; (var init step)
[sum 0 (+ sum i)]) ; Multiple variables
[(>= i 5) sum] ; (test result)
; Optional body (side effects)
(printf "i=~a sum=~a~n" i sum))
; Prints: i=0 sum=0, i=1 sum=0, i=2 sum=1, ...
; Returns: 10
; Building a list
(do ([i 5 (- i 1)]
[lst '() (cons i lst)])
[(zero? i) lst]) ; (1 2 3 4 5)
; Without step (variable unchanged)
(do ([x 10] ; No step = unchanged
[i 0 (+ i 1)])
[(>= i 3) x]) ; 10
Binding Forms Comparison
Form
Scope
Evaluation
Use Case
define
Top-level or local
Immediate
Variables and procedures
let
Local (lexical)
Parallel
Independent bindings
let*
Local (lexical)
Sequential
Dependent bindings
letrec
Local (lexical)
Recursive
Mutually recursive procedures
letrec*
Local (lexical)
Sequential + recursive
Recursive with order guarantees
let-values
Local (lexical)
Parallel
Multiple value bindings
let*-values
Local (lexical)
Sequential
Chained multiple values
define-values
Top-level or local
Immediate
Multiple value definitions
parameterize
Dynamic
For dynamic extent
Fluid/dynamic bindings
let-syntax
Local (lexical)
Compile-time
Local macros
letrec-syntax
Local (lexical)
Compile-time
Mutually recursive local macros
case-lambda
N/A (creates procedure)
Immediate
Multi-arity procedures
do
Local (lexical)
Iterative
Loop with bindings
Decision tree for choosing binding forms:
Best practices for binding forms:
; 1. Use the simplest form that works
(let ([x 1] [y 2]) ...) ; Prefer over let* when order doesn't matter
; 2. Use let* for step-by-step transformations
(let* ([raw (read-input)]
[parsed (parse raw)]
[validated (validate parsed)])
(process validated))
; 3. Use letrec only for recursive procedures
(letrec ([f (lambda (n) ...)]) ; Good: f calls itself
...)
; 4. Use internal defines for cleaner code
(define (process data)
(define (helper x) ...) ; Cleaner than letrec
(define (another y) ...)
(helper (another data)))
; 5. Prefer parameters over fluid-let
(define config (make-parameter default-config))
(parameterize ([config special-config])
(do-work)) ; Safe with continuations
; 6. Use case-lambda for optional arguments
(define greet
(case-lambda
[() (greet "World")]
[(name) (printf "Hello, ~a!~n" name)]))
; 7. Named let for simple loops
(let loop ([lst items] [acc '()])
(if (null? lst)
(reverse acc)
(loop (cdr lst) (cons (f (car lst)) acc))))
Control Structures in Chez Scheme
Conditionals
and and or with short-circuit evaluation:
; and returns first false value or last value
(and #t #t #t) ; #t
(and #t #f #t) ; #f
(and 1 2 3) ; 3
(and) ; #t
; Short-circuit: stops at first false
(and (pair? x)
(number? (car x))
(> (car x) 0)) ; Safe - checks pair? first
; or returns first true value or last value
(or #f #f #t) ; #t
(or #f #f #f) ; #f
(or 1 2 3) ; 1
(or #f 'default) ; default
(or) ; #f
; Short-circuit: stops at first true
(or (cached-value key)
(compute-expensive-value key))
; Default values pattern
(define (greet name)
(printf "Hello, ~a!~n" (or name "World")))
(greet "Alice") ; Hello, Alice!
(greet #f) ; Hello, World!
; Validation chains
(define (valid-input? x)
(and (string? x)
(> (string-length x) 0)
(< (string-length x) 100)
(string-alphabetic? x)))
not and boolean conversions:
; not negates boolean
(not #f) ; #t
(not #t) ; #f
(not '()) ; #f (only #f is false)
(not 0) ; #f (only #f is false)
; Double negation for boolean conversion
(not (not 'anything)) ; #t
(not (not #f)) ; #f
; Explicit boolean check
(define (boolean-value x)
(if x #t #f))
; In Scheme, only #f is false
(if '() 'true 'false) ; true
(if 0 'true 'false) ; true
(if "" 'true 'false) ; true
exclusive-cond (Chez extension) for exhaustive cases:
; exclusive-cond requires exactly one clause to match
; Useful for ensuring complete case coverage
(define (classify n)
(exclusive-cond
[(negative? n) 'negative]
[(zero? n) 'zero]
[(positive? n) 'positive]))
; Raises exception if no clause matches or multiple match
Mapping and Folding
map variants and andmap/ormap:
; andmap - like for-all but returns last value
(andmap even? '(2 4 6)) ; #t
(andmap values '(1 2 3)) ; 3
; ormap - like exists but returns the actual value
(ormap (lambda (x) (and (even? x) x))
'(1 3 5 6 7)) ; 6
; vector-map for vectors
(vector-map add1 '#(1 2 3 4))
; #(2 3 4 5)
(vector-map + '#(1 2 3) '#(10 20 30))
; #(11 22 33)
; string-map for strings (character by character)
(define (string-map proc str)
(list->string
(map proc (string->list str))))
; hash table mapping
(define ht (make-eq-hashtable))
(hashtable-set! ht 'a 1)
(hashtable-set! ht 'b 2)
(vector-map
(lambda (k) (cons k (hashtable-ref ht k #f)))
(hashtable-keys ht))
List comprehension patterns:
; Cartesian product
(define (cartesian-product lst1 lst2)
(apply append
(map (lambda (x)
(map (lambda (y) (list x y))
lst2))
lst1)))
(cartesian-product '(a b) '(1 2 3))
; ((a 1) (a 2) (a 3) (b 1) (b 2) (b 3))
; Filter-map (map + filter)
(define (filter-map proc lst)
(fold-right
(lambda (x acc)
(let ([result (proc x)])
(if result
(cons result acc)
acc)))
'()
lst))
(filter-map (lambda (x)
(and (even? x) (* x 2)))
'(1 2 3 4 5)) ; (4 8)
; Zip multiple lists
(define (zip . lists)
(apply map list lists))
(zip '(1 2 3) '(a b c) '(x y z))
; ((1 a x) (2 b y) (3 c z))
; Enumerate with index
(define (enumerate lst)
(let loop ([lst lst] [i 0])
(if (null? lst)
'()
(cons (cons i (car lst))
(loop (cdr lst) (+ i 1))))))
(enumerate '(a b c)) ; ((0 . a) (1 . b) (2 . c))
; call/1cc creates a one-shot continuation (more efficient)
(call/1cc
(lambda (k)
(k 42))) ; 42
; One-shot means it can only be called once
(define one-shot-k #f)
(call/1cc
(lambda (k)
(set! one-shot-k k)
'initial)) ; initial
(one-shot-k 'resumed) ; resumed
; (one-shot-k 'again) ; ERROR: already used
; Use call/1cc when you only need escape continuation
(define (find-first-1cc pred lst)
(call/1cc
(lambda (return)
(for-each
(lambda (x)
(when (pred x) (return x)))
lst)
#f)))
dynamic-wind for guaranteed cleanup:
; (dynamic-wind before thunk after)
; before and after run on entry/exit, even with continuations
(dynamic-wind
(lambda () (display "entering\n"))
(lambda () (display "body\n") 42)
(lambda () (display "leaving\n")))
; Prints: entering, body, leaving
; Returns: 42
; Resource management
(define (with-file filename proc)
(let ([port #f])
(dynamic-wind
(lambda () (set! port (open-input-file filename)))
(lambda () (proc port))
(lambda () (close-port port)))))
(with-file "data.txt"
(lambda (port)
(get-line port)))
; Re-entry also triggers before/after
(define k #f)
(dynamic-wind
(lambda () (printf "enter~n"))
(lambda ()
(call/cc (lambda (c) (set! k c)))
(printf "body~n"))
(lambda () (printf "exit~n")))
; First: enter, body, exit
(k 'resume)
; Re-entry: enter, body, exit
Continuation marks (for stack inspection):
; Continuation marks attach data to continuations
(define trace-key (make-continuation-mark-key 'trace))
(define (traced-call name thunk)
(with-continuation-mark trace-key name
(thunk)))
(define (get-trace)
(continuation-mark-set->list
(current-continuation-marks)
trace-key))
(traced-call 'outer
(lambda ()
(traced-call 'middle
(lambda ()
(traced-call 'inner
(lambda ()
(get-trace)))))))
; (inner middle outer)
; Useful for debugging, profiling, dynamic scope
let/cc and let/1cc shorthand:
; Chez provides convenient shorthand
(let/cc k
(+ 1 (k 42))) ; 42
; Equivalent to:
(call/cc (lambda (k) (+ 1 (k 42))))
; One-shot version
(let/1cc k
(when (problem?)
(k 'error))
'success)
; Common idiom: early return
(define (validate data)
(let/cc return
(unless (list? data)
(return "must be a list"))
(unless (> (length data) 0)
(return "must be non-empty"))
(unless (for-all number? data)
(return "must contain only numbers"))
#t))
Engines provide timed preemption for computations:
; (make-engine thunk) creates an engine
; An engine is a computation with a fuel limit
(define eng
(make-engine
(lambda ()
(let loop ([n 0])
(loop (+ n 1)))))) ; Infinite loop
; Run engine with fuel (ticks)
; Returns via complete or expire procedure
(eng 1000
(lambda (ticks value) ; complete: finished with fuel left
(printf "Completed with ~a ticks, value: ~a~n" ticks value))
(lambda (new-engine) ; expire: ran out of fuel
(printf "Expired, can resume~n")
new-engine))
; The engine mechanism:
; - Allocate fuel (ticks)
; - Run until fuel exhausted or computation completes
; - If expired, get new engine to continue
Basic engine usage:
; Engine that completes
(define eng1
(make-engine
(lambda ()
(+ 1 2 3))))
(eng1 100
(lambda (ticks val)
(printf "Done! Result: ~a, Remaining: ~a~n" val ticks))
(lambda (e)
(printf "Expired~n")))
; Done! Result: 6, Remaining: ~90
; Engine that needs more fuel
(define eng2
(make-engine
(lambda ()
(let loop ([n 0] [sum 0])
(if (= n 1000000)
sum
(loop (+ n 1) (+ sum n)))))))
; May need multiple runs
(define (run-to-completion engine fuel)
(engine fuel
(lambda (ticks value)
(printf "Complete: ~a~n" value)
value)
(lambda (new-engine)
(printf "Continuing...~n")
(run-to-completion new-engine fuel))))
(run-to-completion eng2 10000)
; Construction
(cons 'a 'b) ; (a . b)
(cons 1 (cons 2 (cons 3 '()))) ; (1 2 3)
; Access
(car '(a b c)) ; a
(cdr '(a b c)) ; (b c)
; Mutation
(define p (cons 1 2))
(set-car! p 10)
p ; (10 . 2)
(set-cdr! p 20)
p ; (10 . 20)
; Composition accessors
(caar '((a b) c)) ; a
(cadr '(a b c)) ; b
(cdar '((a b) c)) ; (b)
(cddr '(a b c)) ; (c)
; Up to four levels
(caaddr '(a (b (c d)))) ; c
(cddddr '(1 2 3 4 5 6)) ; (5 6)
List construction:
; list creates a proper list
(list 1 2 3) ; (1 2 3)
(list) ; ()
(list 'a (+ 1 2) "hello") ; (a 3 "hello")
; list* creates improper list (last element is cdr)
(list* 1 2 3) ; (1 2 . 3)
(list* 1 2 '(3 4)) ; (1 2 3 4)
(list* 'a) ; a
; cons* is alias for list*
(cons* 1 2 3 '()) ; (1 2 3)
; make-list creates list of repeated elements
(make-list 5 'x) ; (x x x x x)
(make-list 3 0) ; (0 0 0)
; iota creates numeric sequence (Chez extension)
(iota 5) ; (0 1 2 3 4)
(iota 5 1) ; (1 2 3 4 5)
(iota 5 0 2) ; (0 2 4 6 8) - start, step
List access and search:
; Length
(length '(a b c d)) ; 4
(length '()) ; 0
; Indexing
(list-ref '(a b c d) 2) ; c
(list-tail '(a b c d) 2) ; (c d)
; First and last
(car '(a b c)) ; a
(last-pair '(a b c)) ; (c)
(list-head '(a b c d) 2) ; (a b) - Chez extension
; Membership (returns tail starting at match)
(memq 'b '(a b c)) ; (b c)
(memv 2 '(1 2 3)) ; (2 3)
(member "b" '("a" "b" "c")) ; ("b" "c")
(memp even? '(1 3 4 5)) ; (4 5)
; Association lists
(assq 'b '((a . 1) (b . 2) (c . 3))) ; (b . 2)
(assv 2 '((1 . a) (2 . b) (3 . c))) ; (2 . b)
(assoc "b" '(("a" . 1) ("b" . 2))) ; ("b" . 2)
(assp (lambda (x) (> x 5)) '((3 . a) (7 . b) (2 . c))) ; (7 . b)
List manipulation:
; Append (creates new pairs except last)
(append '(a b) '(c d)) ; (a b c d)
(append '(a) '(b) '(c)) ; (a b c)
(append '(a b) 'c) ; (a b . c)
; Reverse
(reverse '(a b c)) ; (c b a)
; Copy
(list-copy '(a b c)) ; New copy of list
; Sublist operations (Chez extensions)
(sublist '(a b c d e) 1 4) ; (b c d)
; Remove elements
(remq 'b '(a b c b d)) ; (a c d)
(remv 2 '(1 2 3 2 4)) ; (1 3 4)
(remove "b" '("a" "b" "c")) ; ("a" "c")
(remp odd? '(1 2 3 4 5)) ; (2 4)
; Remove duplicates
(remq-duplicates '(a b a c b)) ; (a b c) - Chez extension
List transformation:
; Flatten (Chez extension)
(flatten '((a b) (c (d e)) f)) ; (a b c d e f)
; Transpose
(define (transpose matrix)
(apply map list matrix))
(transpose '((1 2 3) (4 5 6))) ; ((1 4) (2 5) (3 6))
; Partition
(partition even? '(1 2 3 4 5 6))
; Values: (2 4 6) (1 3 5)
; Split at index
(split-at '(a b c d e) 3) ; Values: (a b c) (d e) - SRFI-1 style
; Take and drop
(define (take lst n)
(if (or (zero? n) (null? lst))
'()
(cons (car lst) (take (cdr lst) (- n 1)))))
(define (drop lst n)
(if (or (zero? n) (null? lst))
lst
(drop (cdr lst) (- n 1))))
(take '(a b c d e) 3) ; (a b c)
(drop '(a b c d e) 3) ; (d e)
Weak pairs and ephemerons:
; Weak pairs - car can be collected if not referenced elsewhere
(define wp (weak-cons 'key 'value))
(weak-pair? wp) ; #t
(car wp) ; key (or #!bwp if collected)
(cdr wp) ; value
(bwp-object? (car wp)) ; #t if car was collected
; Ephemerons - value kept alive only if key is alive
(define ep (ephemeron-cons 'key 'value))
(ephemeron-pair? ep) ; #t
(car ep) ; key
(cdr ep) ; value (collected with key)
; Literal vector
'#(1 2 3) ; #(1 2 3)
#(a b c) ; #(a b c)
; Make vector
(make-vector 5) ; #(0 0 0 0 0) or unspecified
(make-vector 5 'x) ; #(x x x x x)
; From elements
(vector 1 2 3) ; #(1 2 3)
(vector 'a (+ 1 2) "hello") ; #(a 3 "hello")
; From list
(list->vector '(a b c)) ; #(a b c)
; Copy
(vector-copy '#(1 2 3)) ; #(1 2 3) (mutable)
Vector access and modification:
; Length
(vector-length '#(a b c d)) ; 4
; Access
(vector-ref '#(a b c d) 2) ; c
; Mutation
(define v (vector 1 2 3))
(vector-set! v 1 'x)
v ; #(1 x 3)
; Fill
(define v (make-vector 5))
(vector-fill! v 'z)
v ; #(z z z z z)
; Copy region
(define src '#(a b c d e))
(define dst (make-vector 5 '-))
(vector-copy! src 1 dst 2 3)
dst ; #(- - b c d)
Vector operations:
; Append (Chez extension)
(vector-append '#(1 2) '#(3 4)) ; #(1 2 3 4)
; To/from list
(vector->list '#(a b c)) ; (a b c)
(list->vector '(1 2 3)) ; #(1 2 3)
; Map (R6RS)
(vector-map add1 '#(1 2 3)) ; #(2 3 4)
(vector-map + '#(1 2 3) '#(10 20 30)) ; #(11 22 33)
; For-each
(vector-for-each
(lambda (x) (printf "~a " x))
'#(a b c)) ; Prints: a b c
; Sort (Chez extension)
(vector-sort < '#(3 1 4 1 5)) ; #(1 1 3 4 5)
(vector-sort! < (vector 3 1 4 1 5)) ; Sorts in place
; Subvector (Chez extension)
(subvector '#(a b c d e) 1 4) ; #(b c d)
; Stencil vectors store only non-default elements
; The "stencil" is a bitmask indicating which positions have values
; Create stencil vector
; Mask indicates which of 0-57 positions have values
(define sv (stencil-vector #b10101 'a 'b 'c))
; Positions 0, 2, 4 have values 'a, 'b, 'c
; Check if stencil vector
(stencil-vector? sv) ; #t
; Get the mask
(stencil-vector-mask sv) ; #b10101 = 21
; Length (number of actual values stored)
(stencil-vector-length sv) ; 3
; Reference by stencil bit position
(stencil-vector-ref sv #b00001) ; 'a (bit 0)
(stencil-vector-ref sv #b00100) ; 'b (bit 2)
(stencil-vector-ref sv #b10000) ; 'c (bit 4)
Stencil vector operations:
; Update creates new stencil vector (immutable style)
(define sv2 (stencil-vector-update sv #b00010 'new))
; Now has values at positions 0, 1, 2, 4
; Truncate mask (remove positions)
(define sv3 (stencil-vector-truncate sv #b00101))
; Only positions 0 and 2 remain
; Check for specific bits
(fxlogbit? 0 (stencil-vector-mask sv)) ; #t
(fxlogbit? 1 (stencil-vector-mask sv)) ; #f
(fxlogbit? 2 (stencil-vector-mask sv)) ; #t
; Iterate over stencil vector
(define (stencil-vector-for-each proc sv)
(let ([mask (stencil-vector-mask sv)])
(let loop ([bit 0] [idx 0])
(when (< bit 58)
(when (fxlogbit? bit mask)
(proc bit (stencil-vector-ref sv (fxsll 1 bit)))
(loop (+ bit 1) (+ idx 1)))
(unless (fxlogbit? bit mask)
(loop (+ bit 1) idx))))))
Use cases for stencil vectors:
; Stencil vectors are useful for:
; - Sparse data with known position range (0-57)
; - Record-like structures with optional fields
; - Compact representation of small sets
; Example: optional configuration fields
(define config-keys
'((debug . 0) (verbose . 1) (output-file . 2)
(input-file . 3) (timeout . 4)))
(define (make-config . options)
(let loop ([opts options] [mask 0] [values '()])
(if (null? opts)
(apply stencil-vector mask (reverse values))
(let* ([key (caar opts)]
[val (cdar opts)]
[bit (cdr (assq key config-keys))])
(loop (cdr opts)
(fxlogior mask (fxsll 1 bit))
(cons val values))))))
(define cfg (make-config '(debug . #t) '(timeout . 30)))
(stencil-vector-mask cfg) ; Shows which options are set
; Parent record
(define-record-type shape
(fields (immutable color)))
; Child record
(define-record-type (circle shape)
(fields (immutable radius)))
(define c (make-circle 'red 5))
(shape-color c) ; red
(circle-radius c) ; 5
(shape? c) ; #t
(circle? c) ; #t
; Multiple inheritance levels
(define-record-type (filled-circle circle)
(fields (immutable fill-pattern)))
(define fc (make-filled-circle 'blue 10 'solid))
(shape-color fc) ; blue
(circle-radius fc) ; 10
(filled-circle-fill-pattern fc) ; solid
Custom constructors and protocols:
; Custom constructor with protocol
(define-record-type point3d
(fields x y z)
(protocol
(lambda (new)
(case-lambda
[(x y) (new x y 0)] ; Default z to 0
[(x y z) (new x y z)]))))
(make-point3d 1 2) ; z defaults to 0
(make-point3d 1 2 3) ; All specified
; Protocol with parent
(define-record-type (named-point point)
(fields name)
(protocol
(lambda (pnew)
(lambda (name x y)
((pnew x y) name)))))
(define np (make-named-point "origin" 0 0))
(point-x np) ; 0
(named-point-name np) ; "origin"
; Validation in protocol
(define-record-type positive-point
(fields x y)
(protocol
(lambda (new)
(lambda (x y)
(unless (and (positive? x) (positive? y))
(error 'make-positive-point "values must be positive"))
(new x y)))))
Nongenerative and sealed records:
; Nongenerative - same definition produces same type
(define-record-type point-ng
(nongenerative point-ng-uid)
(fields x y))
; Two definitions with same UID are the same type
; Useful for separate compilation
; Sealed - cannot be inherited
(define-record-type final-record
(sealed #t)
(fields value))
; (define-record-type (child final-record) ...) ; Error!
; Opaque - internal structure hidden
(define-record-type opaque-record
(opaque #t)
(fields secret))
; record? returns #f for opaque records
; But type predicate still works
Chez Scheme record extensions:
; Define-record (Chez legacy syntax)
(define-record person (name age))
; Automatically creates:
; make-person, person?, person-name, person-age
; set-person-name!, set-person-age! (mutable by default)
; Record type descriptors
(define rtd (record-type-descriptor person))
(record-type-name rtd) ; person
(record-type-parent rtd) ; #f
(record-type-field-names rtd) ; (name age)
; Reflection
(record-rtd (make-person "Alice" 30)) ; Get RTD from instance
; Create record dynamically
(define p ((record-constructor rtd) "Bob" 25))
((record-accessor rtd 0) p) ; "Bob"
((record-mutator rtd 1) p 26) ; Set age to 26
Record printing (Chez extension):
; Custom record writer
(define-record-type point
(fields x y))
(record-writer (record-type-descriptor point)
(lambda (r port write?)
(fprintf port "#"
(point-x r) (point-y r))))
(make-point 3 4) ; #
; Generic record printing
(record-print-procedure) ; Current print procedure
(parameterize ([print-record #t])
(printf "~s" (make-point 3 4))) ; Shows structure
Record Equality and Hashing
Default record equality:
; By default, records use eq? (identity)
(define-record-type point (fields x y))
(define p1 (make-point 3 4))
(define p2 (make-point 3 4))
(eq? p1 p1) ; #t
(eq? p1 p2) ; #f (different objects)
(eqv? p1 p2) ; #f
(equal? p1 p2) ; #f (by default)
Custom equality with record-equal-procedure:
; Set custom equality for record type
(define-record-type point
(fields x y))
(record-type-equal-procedure
(record-type-descriptor point)
(lambda (p1 p2 equal?)
(and (equal? (point-x p1) (point-x p2))
(equal? (point-y p1) (point-y p2)))))
; Now equal? works on points
(define p1 (make-point 3 4))
(define p2 (make-point 3 4))
(equal? p1 p2) ; #t
; Recursive equality with equal? parameter
(define-record-type tree
(fields value left right))
(record-type-equal-procedure
(record-type-descriptor tree)
(lambda (t1 t2 equal?)
(and (equal? (tree-value t1) (tree-value t2))
(equal? (tree-left t1) (tree-left t2))
(equal? (tree-right t1) (tree-right t2)))))
Custom hashing with record-hash-procedure:
; Set custom hash for record type
(record-type-hash-procedure
(record-type-descriptor point)
(lambda (p hash)
(fxxor (hash (point-x p))
(fxsll (hash (point-y p)) 16))))
; Now points can be hashtable keys with equal-hash
(define ht (make-hashtable equal-hash equal?))
(hashtable-set! ht (make-point 1 2) 'value)
(hashtable-ref ht (make-point 1 2) #f) ; 'value
; Combined equal and hash for complex record
(define-record-type person
(fields name age address))
(record-type-equal-procedure
(record-type-descriptor person)
(lambda (p1 p2 equal?)
(and (equal? (person-name p1) (person-name p2))
(equal? (person-age p1) (person-age p2))
(equal? (person-address p1) (person-address p2)))))
(record-type-hash-procedure
(record-type-descriptor person)
(lambda (p hash)
(fxxor (hash (person-name p))
(fxxor (hash (person-age p))
(hash (person-address p))))))
Generic record comparison helpers:
; Helper to compare all fields
(define (make-record-equal rtd)
(let ([accessors (map (lambda (i)
(record-accessor rtd i))
(iota (length (record-type-field-names rtd))))])
(lambda (r1 r2 equal?)
(for-all (lambda (acc)
(equal? (acc r1) (acc r2)))
accessors))))
; Helper to hash all fields
(define (make-record-hash rtd)
(let ([accessors (map (lambda (i)
(record-accessor rtd i))
(iota (length (record-type-field-names rtd))))])
(lambda (r hash)
(fold-left (lambda (h acc)
(fxxor h (hash (acc r))))
0
accessors))))
; Apply to a record type
(let ([rtd (record-type-descriptor my-record)])
(record-type-equal-procedure rtd (make-record-equal rtd))
(record-type-hash-procedure rtd (make-record-hash rtd)))
Structural equality considerations:
; Be careful with mutable fields
(define-record-type mutable-point
(fields (mutable x) (mutable y)))
; If using as hashtable key, mutating changes hash!
(define ht (make-hashtable equal-hash equal?))
(define mp (make-mutable-point 1 2))
(hashtable-set! ht mp 'value)
(hashtable-ref ht mp #f) ; 'value
(mutable-point-x-set! mp 100) ; Mutation!
(hashtable-ref ht mp #f) ; #f - hash changed!
; Solution: use immutable records as keys, or
; hash only immutable fields
; Test if bit is set
(bitwise-bit-set? #b1010 1) ; #t (bit 1 is 1)
(bitwise-bit-set? #b1010 2) ; #f (bit 2 is 0)
(logbit? 1 #b1010) ; #t (alias, args reversed)
; Count bits
(bitwise-bit-count #b1010) ; 2 (number of 1s)
(logcount #b1010) ; 2 (alias)
(bitwise-bit-count -1) ; -1 (infinite 1s)
; Bit length (minimum bits to represent)
(bitwise-length #b1010) ; 4
(integer-length #b1010) ; 4 (alias)
(bitwise-length 0) ; 0
(bitwise-length -1) ; 0
(bitwise-length -8) ; 3
; First bit set (lowest)
(bitwise-first-bit-set #b1010) ; 1
(bitwise-first-bit-set #b1000) ; 3
(bitwise-first-bit-set 0) ; -1
; Copy bit (set or clear specific bit)
(bitwise-copy-bit 0 #b1010 1) ; #b1011 (set bit 0)
(bitwise-copy-bit 1 #b1010 0) ; #b1000 (clear bit 1)
Bit field operations:
; Extract bit field
(bitwise-bit-field #b110101 2 5) ; #b101 = 5 (bits 2-4)
(bit-field #b110101 2 5) ; Same (alias)
; Bit field width = 5-2 = 3 bits extracted
; Position 2 becomes position 0 in result
; Replace bit field
(bitwise-copy-bit-field #b110101 2 5 #b010) ; #b101001
; Replaces bits 2-4 with #b010
; Rotate bit field
(bitwise-rotate-bit-field #b110101 2 5 1)
; Rotate bits 2-4 left by 1
; Reverse bit field
(bitwise-reverse-bit-field #b110101 0 6)
; Reverse bits 0-5
; Practical example: extract RGB from 24-bit color
(define (color-rgb color)
(values
(bitwise-bit-field color 16 24) ; Red
(bitwise-bit-field color 8 16) ; Green
(bitwise-bit-field color 0 8))) ; Blue
(color-rgb #xFF8040) ; Values: 255, 128, 64
Bitwise conditionals:
; Bitwise if (ternary)
(bitwise-if #b1100 #b1010 #b0110) ; #b1010
; Where mask=1, take from arg2; where mask=0, take from arg3
; Equivalently:
(bitwise-ior
(bitwise-and #b1100 #b1010) ; Bits from arg2 where mask=1
(bitwise-and (bitwise-not #b1100) #b0110)) ; Bits from arg3 where mask=0
; NAND, NOR, etc. (combinations)
(define (bitwise-nand a b)
(bitwise-not (bitwise-and a b)))
(define (bitwise-nor a b)
(bitwise-not (bitwise-ior a b)))
(define (bitwise-xnor a b)
(bitwise-not (bitwise-xor a b)))
(define (bitwise-andc1 a b) ; AND with complement of first
(bitwise-and (bitwise-not a) b))
(define (bitwise-orc1 a b) ; OR with complement of first
(bitwise-ior (bitwise-not a) b))
; Random integer in range [0, n)
(random 100) ; 0-99
(random 6) ; 0-5 (dice roll - 1)
; Random real in [0.0, 1.0)
(random 1.0) ; e.g., 0.7234...
; Random exact rational
(random 1/1) ; Exact rational in [0, 1)
; Multiple calls give different values
(list (random 100) (random 100) (random 100))
; e.g., (42 17 89)
Random state management:
; Get current random state
(random-seed) ; Current seed (implementation-dependent)
; Set random seed (for reproducibility)
(random-seed 12345)
(random 100) ; Same result every time with same seed
; Save and restore state
(define saved-seed (random-seed))
(list (random 100) (random 100)) ; e.g., (42 17)
(random-seed saved-seed)
(list (random 100) (random 100)) ; Same: (42 17)
; Different seed, different sequence
(random-seed 99999)
(list (random 100) (random 100)) ; Different values
Random distributions and utilities:
; Random in range [a, b)
(define (random-range a b)
(+ a (random (- b a))))
(random-range 10 20) ; 10-19
; Random float in range [a, b)
(define (random-float-range a b)
(+ a (* (random 1.0) (- b a))))
(random-float-range 0.0 100.0) ; e.g., 47.23...
; Random boolean
(define (random-bool)
(zero? (random 2)))
(random-bool) ; #t or #f
; Random choice from list
(define (random-choice lst)
(list-ref lst (random (length lst))))
(random-choice '(red green blue)) ; Random element
; Random sample (n items without replacement)
(define (random-sample lst n)
(let loop ([remaining lst] [count n] [result '()])
(cond
[(or (zero? count) (null? remaining)) (reverse result)]
[(< (random 1.0) (/ count (length remaining)))
(loop (cdr remaining) (- count 1) (cons (car remaining) result))]
[else
(loop (cdr remaining) count result)])))
(random-sample '(1 2 3 4 5 6 7 8 9 10) 3) ; e.g., (2 5 9)
; EOL styles for line ending conversion
; 'none - No conversion
; 'lf - Unix style (linefeed)
; 'cr - Old Mac style (carriage return)
; 'crlf - Windows style (carriage return + linefeed)
; 'nel - Unicode next line
; 'crnel - CR + NEL
; 'ls - Unicode line separator
; Native EOL style
(native-eol-style) ; 'lf on Unix, 'crlf on Windows
; Create transcoder with specific EOL
(make-transcoder
(utf-8-codec)
'crlf) ; Windows-style line endings
; EOL conversion on input:
; - All EOL sequences converted to #\newline
; EOL conversion on output:
; - #\newline converted to specified style
Error handling modes:
; Error modes for invalid byte sequences
; 'ignore - Skip invalid bytes
; 'raise - Raise exception
; 'replace - Insert replacement character (U+FFFD)
; Strict transcoder (raise on error)
(make-transcoder
(utf-8-codec)
'none
'raise)
; Lenient transcoder (replace invalid)
(make-transcoder
(utf-8-codec)
'none
'replace)
; Using transcoders with ports
(open-file-input-port "data.txt"
(file-options)
'block
(make-transcoder (utf-8-codec)))
; Transcoded port (wrap binary port)
(define bin-port (open-file-input-port "data.txt"))
(define text-port
(transcoded-port bin-port (make-transcoder (utf-8-codec))))
; Get transcoder from port
(port-transcoder text-port) ; Returns transcoder or #f
; Close any port
(close-port port)
; Close specific direction
(close-input-port port) ; Close input side
(close-output-port port) ; Close output side
; Check if port is closed (Chez extension)
(port-closed? port) ; #t if closed
; Ports and garbage collection
; Ports are closed when garbage collected, but
; explicit closing is recommended for resources
; Safe port handling with dynamic-wind
(define (call-with-port port proc)
(dynamic-wind
(lambda () #f)
(lambda () (proc port))
(lambda () (close-port port))))
; Built-in call-with-* procedures
(call-with-input-file "data.txt"
(lambda (port)
(get-line port)))
(call-with-output-file "data.txt"
(lambda (port)
(display "Hello" port)))
Port positioning:
; Check if port supports positioning
(port-has-port-position? port)
(port-has-set-port-position!? port)
; Get current position
(port-position port) ; Returns position
; Set position
(set-port-position! port 0) ; Go to beginning
(set-port-position! port 100) ; Go to byte/char 100
; Position is in:
; - Bytes for binary ports
; - Characters for some textual ports
; - May be opaque for textual ports (codec-dependent)
; Seek from different origins (Chez extension)
(file-position port) ; Current position
(file-position port 0) ; Seek to beginning
(file-position port pos) ; Seek to position
; File length (Chez extension)
(file-length port) ; Total length in bytes
Port buffer control:
; Flush output buffer
(flush-output-port port)
(flush-output-port) ; Flush current-output-port
; Check for available input
(input-port-ready? port) ; #t if data available without blocking
; Clear input buffer (Chez extension)
(clear-input-port port)
(clear-input-port) ; Clear current-input-port
; Clear output buffer without writing (Chez extension)
(clear-output-port port)
(clear-output-port) ; Clear current-output-port
; Port buffer mode
(output-port-buffer-mode port) ; 'none, 'line, or 'block
; Set buffer mode (Chez extension)
(set-port-output-buffer-mode! port 'line)
Port names and information:
; Get port name (for error messages, debugging)
(port-name port) ; Returns string
; Chez extensions for port info
(port-file-descriptor port) ; OS file descriptor (if applicable)
; Check port EOF status
(port-eof? port) ; #t if at end of file
; Mark port as EOF (Chez extension)
(set-port-eof! port #t)
; Input port position tracking
(port-has-port-length? port) ; Can get length?
(port-length port) ; Get length if available
; Control read behavior with parameters
(parameterize ([case-sensitive #t]) ; Preserve symbol case
(read (open-input-string "Hello"))) ; Hello (not hello)
; Read with source information (Chez extension)
(read-token port) ; Returns token type and value
; Skip whitespace and comments
(define (skip-whitespace port)
(let ([c (lookahead-char port)])
(when (and (char? c) (char-whitespace? c))
(get-char port)
(skip-whitespace port))))
; Check for EOF
(eof-object? (read port)) ; #t at end of file
(port-eof? port) ; #t if at EOF (Chez extension)
Output Operations
Writing bytes (binary output):
; Write single byte
(put-u8 port 65) ; Write byte 65 ('A')
; Write bytevector
(put-bytevector port #vu8(1 2 3 4 5))
; Write portion of bytevector
(put-bytevector port bv start count)
; Example
(let ([port (open-bytevector-output-port)])
(put-u8 port 72) ; H
(put-u8 port 105) ; i
(get-output-bytevector port))
; #vu8(72 105)
Writing characters (textual output):
; Write single character
(put-char port #\A)
(write-char #\A port) ; R5RS name
(write-char #\A) ; To current-output-port
; Write string
(put-string port "Hello")
(put-string port str start count) ; Portion of string
; Write newline
(newline port)
(newline) ; To current-output-port
; Example
(let ([port (open-output-string)])
(put-char port #\H)
(put-string port "ello")
(newline port)
(get-output-string port)) ; "Hello\n"
; pretty-print formats Scheme data nicely
(pretty-print '(define (factorial n)
(if (zero? n)
1
(* n (factorial (- n 1))))))
; Prints with proper indentation
; Compare with write
(write '(define (factorial n) (if (zero? n) 1 (* n (factorial (- n 1))))))
; Prints on one line
; Pretty-print to specific port
(pretty-print datum port)
; Pretty-print to string
(with-output-to-string
(lambda ()
(pretty-print '(1 2 3))))
Pretty printing parameters:
; Line width
(pretty-line-length) ; Get current (default 75)
(pretty-line-length 100) ; Set to 100
(parameterize ([pretty-line-length 40])
(pretty-print long-expression))
; Initial indentation
(pretty-initial-indent 4) ; Start with 4 spaces
; One-line limit (try to fit on one line if under this)
(pretty-one-line-limit) ; Get
(pretty-one-line-limit 60) ; Set
; Maximum depth
(print-level) ; Maximum nesting depth
(parameterize ([print-level 3])
(pretty-print deeply-nested)) ; Shows ... for deeper
; Maximum length
(print-length) ; Maximum list length
(parameterize ([print-length 10])
(pretty-print long-list)) ; Shows ... after 10
Pretty format control:
; pretty-format sets formatting for specific forms
(pretty-format 'my-special-form
'(my-special-form (bracket x ...) #f body ...))
; Format specifiers:
; (bracket x ...) - arguments on same line
; #f - break here
; body ... - indented body expressions
; Built-in formats
(pretty-format 'define) ; See how define is formatted
(pretty-format 'lambda) ; See how lambda is formatted
(pretty-format 'let) ; See how let is formatted
; Custom formatting for DSL
(pretty-format 'when-valid
'(when-valid test #f body ...))
; Reset to default
(pretty-format 'my-form #f)
; format returns string
(format "Hello, ~a!" "World") ; "Hello, World!"
; printf prints to current-output-port
(printf "Hello, ~a!~n" "World")
; fprintf prints to specific port
(fprintf port "Value: ~a~n" 42)
; errorf prints to current-error-port and raises error
(errorf 'proc "Error: ~a" message)
; warningf prints warning
(warningf 'proc "Warning: ~a" message)
; Flush output buffer
(flush-output-port port)
(flush-output-port) ; Current output port
; Get buffer mode
(output-port-buffer-mode port) ; 'none, 'line, 'block
; Block vs line buffering
; - block: flush when buffer full
; - line: flush on newline
; - none: write immediately
; Example: ensure prompt appears before input
(display "Enter name: ")
(flush-output-port)
(read-line)
; Clear input buffer (Chez extension)
(clear-input-port port) ; Discard buffered input
; Clear output buffer (Chez extension)
(clear-output-port port) ; Discard without writing
Port positioning:
; File position
(file-position port) ; Get current position
(file-position port 0) ; Seek to beginning
(file-position port 100) ; Seek to byte 100
; Chez extension: seek from end
(file-position port (- (file-length port) 10)) ; 10 from end
; Standard R6RS positioning
(port-has-port-position? port) ; Can get position?
(port-position port) ; Get position
(port-has-set-port-position!? port) ; Can set?
(set-port-position! port 0) ; Set position
; Truncate file at current position (Chez extension)
(truncate-port port)
; File length (Chez extension)
(file-length port) ; Total file size
; Set file length (Chez extension)
(set-file-length! port 100) ; Truncate or extend to 100 bytes
Port status and information:
; Check for available input (non-blocking)
(input-port-ready? port) ; #t if data available
(char-ready? port) ; Same for textual ports
; Check for EOF
(port-eof? port) ; #t if at end of file
; Port name (for error messages)
(port-name port) ; Returns string
; Port file descriptor (Chez extension)
(port-file-descriptor port) ; OS file descriptor or #f
; Check if port is a file port
(file-port? port) ; #t if backed by file
; Mark port as closed state
(mark-port-closed! port) ; Chez extension
Timeout operations (Chez extension):
; Set read timeout
(set-port-input-timeout! port seconds)
; Non-blocking mode
(set-port-input-timeout! port 0) ; Return immediately
; Example: read with timeout
(define (read-with-timeout port seconds)
(set-port-input-timeout! port seconds)
(let ([result (guard (c [#t #f])
(read port))])
(set-port-input-timeout! port #f) ; Reset
result))
; Check if input ready before blocking read
(define (safe-read port)
(if (input-port-ready? port)
(read port)
'no-input))
Fasl Output
Fasl (Fast Load) format:
; Fasl is a compact binary format for Scheme data
; Used for compiled files, faster than text read/write
; Write fasl output
(fasl-write datum port)
; Read fasl input
(fasl-read port) ; Returns datum
; Example: save and restore data
(call-with-port
(open-file-output-port "data.fasl"
(file-options no-fail))
(lambda (port)
(fasl-write '(1 2 3 4 5) port)
(fasl-write '#(a b c) port)))
(call-with-port
(open-file-input-port "data.fasl")
(lambda (port)
(list (fasl-read port)
(fasl-read port))))
; ((1 2 3 4 5) #(a b c))
; Fasl version (Chez extension)
(fasl-version) ; Current fasl format version
; Cross-compilation fasl
; Fasl files are platform-specific by default
; Strip fasl file (remove debugging info)
(strip-fasl-file "input.so" "output.so")
; Fasl can store most Scheme objects:
; - Lists, vectors, bytevectors
; - Strings, symbols, numbers
; - Records (with proper setup)
; - Procedures (as compiled code)
; - Hashtables
; Cannot store:
; - Ports
; - Foreign pointers
; - Locked objects
File System Interface
File existence and type:
; Check file existence
(file-exists? "data.txt") ; #t if exists
; File type
(file-regular? "data.txt") ; #t if regular file
(file-directory? "mydir") ; #t if directory
(file-symbolic-link? "link") ; #t if symlink
; Get file type
(file-type "path") ; 'regular, 'directory, 'symbolic-link,
; 'char-device, 'block-device,
; 'fifo, 'socket
; Follow symlinks
(file-type "path" #t) ; Follow symlinks
File information:
; File size
(file-size "data.txt") ; Size in bytes (Chez extension)
; Modification time
(file-modification-time "data.txt") ; Time value
; Access time
(file-access-time "data.txt")
; Change time
(file-change-time "data.txt")
; Compare times
(time<? (file-modification-time "a.txt")
(file-modification-time "b.txt"))
; Convert to date
(date-and-time (file-modification-time "data.txt"))
; Complete Chez Scheme functionality
(import (chezscheme))
; This provides everything in (rnrs) plus Chez extensions:
; - Additional I/O procedures
; - Compilation and evaluation
; - Foreign function interface
; - Threading support
; - Debugging and inspection
; - System interface
; - Engine support
; - Extended record facilities
; - Hash table extensions
; - And much more
; Common pattern: use chezscheme for full access
(library (my-library)
(export my-proc)
(import (chezscheme)) ; Full Chez Scheme
(define (my-proc x)
(printf "Processing ~a~n" x) ; Chez extension
(* x 2)))
SRFI libraries:
; Some SRFIs are built-in or easily available
; SRFI-1: List library (partial support via rnrs)
(import (rnrs lists))
; Using Chez's built-in equivalents
(import (chezscheme))
; Provides: fold-left, fold-right, filter, partition,
; find, exists, for-all, memp, remp, assp, etc.
; Pattern for SRFI compatibility
(library (srfi :1)
(export
; List constructors
cons list
; Selectors
car cdr
; ... etc
)
(import (rnrs) (rnrs lists)))
; Some Chez installations include SRFI collections
; Check your installation for available SRFIs
Library naming conventions:
; Library names are lists of identifiers
(import (rnrs)) ; Single identifier
(import (rnrs base)) ; Two identifiers
(import (rnrs io ports)) ; Three identifiers
; With version
(import (rnrs (6))) ; Version 6
(import (rnrs base (6))) ; rnrs base version 6
; Version matching
(import (rnrs (>= 6))) ; Version 6 or higher
(import (mylib (and (>= 1) (< 2)))) ; 1.x versions
; Common naming patterns
(library (company project module) ...)
(library (myapp utils string) ...)
(library (web server http) ...)
Library summary table:
Library
Description
(rnrs)
Complete R6RS standard library
(rnrs base)
Core syntax, procedures, basic types
(rnrs lists)
List operations: find, filter, fold, etc.
(rnrs io ports)
Port-based I/O system
(rnrs io simple)
Simple I/O: read, write, display
(rnrs records syntactic)
define-record-type syntax
(rnrs exceptions)
guard, raise, with-exception-handler
(rnrs hashtables)
Hash table operations
(rnrs syntax-case)
Macro system
(chezscheme)
Full Chez Scheme with extensions
Running Top-level Programs
Top-level program structure:
; A top-level program starts with import and has a body
; File: hello.ss
#!r6rs
(import (rnrs))
(display "Hello, World!")
(newline)
; Or with Chez Scheme extensions
#!chezscheme
(import (chezscheme))
(printf "Hello, World!~n")
Running programs from command line:
# Run as script
$ scheme --script hello.ss
# Run as program (R6RS compliant)
$ scheme --program hello.ss
# With arguments
$ scheme --script myprogram.ss arg1 arg2 arg3
# Using petite (no compiler)
$ petite --script hello.ss
# Make executable script (Unix)
$ chmod +x hello.ss
$ ./hello.ss
Shebang for executable scripts:
#!/usr/bin/env scheme-script
(import (chezscheme))
; Access command-line arguments
(define args (command-line-arguments))
(printf "Arguments: ~a~n" args)
; Program name
(printf "Program: ~a~n" (car (command-line)))
; Exit with status
(exit 0)
; Alternative shebangs:
; #!/usr/bin/scheme --script
; #!/usr/local/bin/scheme --script
; #!/usr/bin/env scheme --script
Accessing command-line arguments:
#!/usr/bin/env scheme-script
(import (chezscheme))
; (command-line) returns all arguments including program name
(define all-args (command-line))
; ("program-name" "arg1" "arg2" ...)
; (command-line-arguments) returns just the arguments
(define args (command-line-arguments))
; ("arg1" "arg2" ...)
; Example: process arguments
(define (main args)
(cond
[(null? args)
(display "Usage: program \n")
(exit 1)]
[(string=? (car args) "--help")
(display "Help message here\n")
(exit 0)]
[else
(process-file (car args))
(exit 0)]))
(main (command-line-arguments))
Program exit and return codes:
; Exit normally
(exit) ; Exit with status 0
(exit 0) ; Same
; Exit with error
(exit 1) ; Non-zero indicates error
; Exit with any value (Chez extension)
(exit 'some-value) ; Converted to exit status
; Graceful shutdown
(define (cleanup-and-exit status)
(close-all-ports)
(delete-temp-files)
(exit status))
; Handling errors at top level
(guard (c
[(error? c)
(display "Error: " (current-error-port))
(display (condition-message c) (current-error-port))
(newline (current-error-port))
(exit 1)])
(main (command-line-arguments)))
Running programs from REPL:
; Load and run program
(load-program "myprogram.ss")
; Load compiled program
(load-program "myprogram.so")
; Run with custom arguments
(parameterize ([command-line '("prog" "arg1" "arg2")])
(load-program "myprogram.ss"))
; Compile program
(compile-program "myprogram.ss") ; Creates myprogram.so
(compile-program "myprogram.ss" "out.so") ; Custom output
; Compile and load
(load (compile-program "myprogram.ss"))
Library and Top-level Program Forms
Library form syntax:
; Basic library structure
(library (library-name)
(export export-spec ...)
(import import-spec ...)
; Library body: definitions
definition ...
; Optional: expressions (run when library instantiated)
expression ...)
; Complete example
(library (math arithmetic)
(export add subtract multiply divide
square cube)
(import (rnrs))
(define (add a b) (+ a b))
(define (subtract a b) (- a b))
(define (multiply a b) (* a b))
(define (divide a b) (/ a b))
(define (square x) (* x x))
(define (cube x) (* x x x)))
(library (my-library)
(export ...)
(import
; Simple import
(rnrs)
; Import specific bindings only
(only (rnrs lists) filter map fold-left)
; Import all except some
(except (rnrs) set-car! set-cdr!)
; Import with prefix
(prefix (rnrs lists) list:)
; Now use: list:filter, list:map, etc.
; Import with rename
(rename (rnrs) (lambda fn) (define def))
; Now use: (fn (x) ...), (def name value)
; Combine specifications
(prefix (only (rnrs lists) filter map) l:)
; Now use: l:filter, l:map
; Version specification
(rnrs (6))
(mylib (>= 1 2)) ; Version 1.2 or higher
)
...)
Top-level program form:
; Top-level program structure
#!r6rs
(import import-spec ...)
; Program body
definition ...
expression ...
; Example program
#!r6rs
(import
(rnrs)
(rnrs programs) ; For command-line, exit
(my-library))
; Definitions
(define (process-file filename)
(call-with-input-file filename
(lambda (port)
(let loop ([line (get-line port)])
(unless (eof-object? line)
(display line)
(newline)
(loop (get-line port)))))))
; Main code
(let ([args (command-line-arguments)])
(when (null? args)
(display "No file specified\n")
(exit 1))
(for-each process-file args))
Library with initialization:
; Libraries can have expressions that run on instantiation
(library (config loader)
(export get-config config-loaded?)
(import (chezscheme))
; Private state
(define config-data #f)
(define loaded? #f)
; Exported procedures
(define (get-config key)
(and loaded? (assq key config-data)))
(define (config-loaded?)
loaded?)
; Initialization code (runs when library first used)
(display "Loading configuration...\n")
(set! config-data
(if (file-exists? "config.scm")
(with-input-from-file "config.scm" read)
'()))
(set! loaded? #t)
(printf "Config loaded: ~a entries~n"
(length config-data)))
Nested library definitions (Chez extension):
; Define library at REPL or in another file
(define-library (my utils)
(export helper-proc)
(import (chezscheme))
(begin
(define (helper-proc x)
(* x 2))))
; R7RS-style library syntax (limited support)
(define-library (my-r7rs-lib)
(export foo bar)
(import (scheme base))
(begin
(define (foo x) x)
(define (bar x) (+ x 1))))
Standalone Import and Export Forms
Interactive imports at REPL:
; Import at top level (REPL)
(import (rnrs))
(import (chezscheme))
; Import specific bindings
(import (only (rnrs lists) filter partition))
; Import with prefix
(import (prefix (rnrs hashtables) ht:))
(ht:make-eq-hashtable) ; Use prefixed name
; Multiple imports
(import (rnrs) (rnrs mutable-pairs))
; Re-import to update (after library recompile)
(import (my-library))
Implicit exports (Chez extension):
; implicit-exports parameter controls default export behavior
(implicit-exports) ; Check current setting
; When #t (default), all definitions are implicitly exported
; unless explicitly listed in export clause
(parameterize ([implicit-exports #f])
; In this context, only explicit exports are visible
(eval '(library (strict-lib)
(export public-proc) ; Only this is exported
(import (chezscheme))
(define (public-proc x) (private-proc x))
(define (private-proc x) (* x 2)))
(interaction-environment)))
Export from REPL (Chez extension):
; Define at top level
(define (my-utility x)
(+ x 1))
; This is available in the interaction environment
; but not as a proper library export
; To make definitions available as a library,
; create a proper library file
; Or use top-level-program for scripts
(top-level-program
(import (chezscheme))
(define (helper x) (* x 2))
(printf "Result: ~a~n" (helper 21)))
Indirect exports:
(library (shapes)
(export make-shape shape? shape-area
make-circle circle?
make-rectangle rectangle?)
(import (chezscheme))
; Parent record type
(define-record-type shape
(fields (immutable type)))
; Circle - extends shape conceptually
(define-record-type circle
(fields radius))
(define-record-type rectangle
(fields width height))
; Generic area procedure
(define (shape-area s)
(cond
[(circle? s)
(* 3.14159 (square (circle-radius s)))]
[(rectangle? s)
(* (rectangle-width s) (rectangle-height s))]
[else (error 'shape-area "unknown shape" s)]))
(define (square x) (* x x)))
; User imports make-circle but also gets circle?
; through the export list
Import levels:
; Import levels control when bindings are available
; Level 0: run time (default)
; Level 1: expand time (macro expansion)
(library (my-macros)
(export define-checked)
(import
(rnrs)
(for (rnrs syntax-case) expand) ; Need at expand time
(for (rnrs base) run expand)) ; Need at both times
(define-syntax define-checked
(lambda (stx)
(syntax-case stx ()
[(_ (name arg ...) pred body ...)
#'(define (name arg ...)
(unless (and (pred arg) ...)
(error 'name "invalid argument"))
body ...)]))))
; Shorthand for common cases
(import (for (my-helper) expand)) ; Only at expand time
(import (for (my-helper) run)) ; Only at run time (default)
(import (for (my-helper) (meta 2))) ; Two levels up
Explicitly Invoking Libraries
Library invocation basics:
; Libraries are normally invoked automatically on import
; But can be controlled explicitly
; Visit: evaluate syntax definitions only
(visit "my-library.so")
(visit "my-library.ss")
; Revisit: visit again (re-evaluate)
(revisit "my-library.so")
; Load library (Chez extension)
(load-library "my-library.ss")
; Invoke library explicitly
(invoke-library '(my-library))
Library phases:
; Library lifecycle:
; 1. Loading - read source/compiled code
; 2. Visiting - evaluate syntax definitions
; 3. Invoking - evaluate variable definitions and expressions
; When you import a library:
; - It's visited if macros are used
; - It's invoked if run-time values are used
; Force visit (for macro-only libraries)
(import (for (macro-library) expand))
; Example showing phases
(library (phase-demo)
(export get-value demo-macro)
(import (chezscheme))
; This runs during visit (syntax definition)
(define-syntax demo-macro
(begin
(printf "Defining macro (visit time)~n")
(syntax-rules ()
[(_ x) (list 'demo x)])))
; This runs during invoke (variable definition)
(define get-value
(begin
(printf "Defining procedure (invoke time)~n")
(lambda () 42)))
; This expression runs during invoke
(printf "Library body expression (invoke time)~n"))
Controlling library instantiation:
; library-list: get list of loaded libraries
(library-list) ; ((rnrs) (chezscheme) ...)
; Check if library is loaded
(member '(my-library) (library-list))
; library-exports: get exported bindings
(library-exports '(rnrs lists))
; (find for-all exists filter partition ...)
; library-requirements: get dependencies
(library-requirements '(my-library))
; Returns list of required libraries
; library-object-filename: get compiled file path
(library-object-filename '(my-library))
; library-version: get version if specified
(library-version '(rnrs)) ; (6)
Library reloading:
; During development, you may need to reload libraries
; Reload source file
(load "my-library.ss")
; Recompile and reload
(compile-library "my-library.ss")
(load "my-library.so")
; Reset library to force re-instantiation (Chez extension)
(library-requirements-options 'no-invoke)
; Then re-import
; Clear all libraries (drastic, use carefully)
; Not directly supported - restart Scheme
; Practical reloading workflow
(define (reload-library libname filename)
(compile-library filename)
(load (path-root filename) ".so")
(eval `(import ,libname)
(interaction-environment)))
; Get library version
(library-version '(rnrs)) ; (6)
(library-version '(my-library)) ; () if no version
; Get object (compiled) filename
(library-object-filename '(my-library))
; "/path/to/my-library.so" or #f
; Get source filename (if known)
; Not directly available, but can derive:
(define (library-source-filename lib)
(let ([obj (library-object-filename lib)])
(and obj
(let ([base (path-root obj)])
(find file-exists?
(map (lambda (ext) (string-append base ext))
'(".ss" ".sls" ".scm")))))))
; Check if library is from file vs built-in
(define (file-library? lib)
(and (library-object-filename lib) #t))
; Re-export all public bindings
(library (my-project)
(export
; From core
make-widget widget? widget-name widget-value widget-process
; From utils
string-split string-join string-trim with-timing
; From main
run process-all)
(import
(my-project core)
(my-project utils)
(my-project main)))
; splicing-fluid-let-syntax splices into surrounding context
(define-syntax with-alternative-if
(syntax-rules ()
[(_ body ...)
(splicing-fluid-let-syntax
([if (syntax-rules ()
[(_ test then else)
(cond [test then] [else else])])])
body ...)]))
; Can be used at top level or in definition context
(with-alternative-if
(define (safe-divide a b)
(if (zero? b)
'undefined
(/ a b))))
; splicing version allows definitions to escape
; ... matches zero or more
(define-syntax my-list
(syntax-rules ()
[(_ x ...)
(list x ...)]))
(my-list 1 2 3) ; (1 2 3)
(my-list) ; ()
; Nested ellipsis
(define-syntax my-let*
(syntax-rules ()
[(_ () body ...)
(begin body ...)]
[(_ ([var val] rest ...) body ...)
(let ([var val])
(my-let* (rest ...) body ...))]))
; Ellipsis in both pattern and template
(define-syntax swap-pairs
(syntax-rules ()
[(_ (a b) ...)
(list (list b a) ...)]))
(swap-pairs (1 2) (3 4) (5 6)) ; ((2 1) (4 3) (6 5))
Literal identifiers:
; Literals match exactly (compared with free-identifier=?)
(define-syntax my-case
(syntax-rules (else) ; else is a literal
[(_ expr [else e1 e2 ...])
(begin e1 e2 ...)]
[(_ expr [(datum ...) e1 e2 ...])
(if (memv expr '(datum ...))
(begin e1 e2 ...))]
[(_ expr [(datum ...) e1 e2 ...] clause ...)
(let ([t expr])
(if (memv t '(datum ...))
(begin e1 e2 ...)
(my-case t clause ...)))]))
; Multiple literals
(define-syntax for
(syntax-rules (in from to by)
[(_ var in lst body ...)
(for-each (lambda (var) body ...) lst)]
[(_ var from start to end body ...)
(do ([var start (+ var 1)])
((> var end))
body ...)]
[(_ var from start to end by step body ...)
(do ([var start (+ var step)])
((> var end))
body ...)]))
(for i in '(1 2 3) (display i))
(for i from 1 to 5 (display i))
(for i from 0 to 10 by 2 (display i))
Underscore and ellipsis escaping:
; _ matches anything but doesn't bind
(define-syntax first-of-two
(syntax-rules ()
[(_ a _) a])) ; Second element ignored
(first-of-two 1 2) ; 1
; Escape ellipsis with (... ...)
(define-syntax define-quoter
(syntax-rules ()
[(_ name)
(define-syntax name
(syntax-rules ()
[(_ x (... ...)) ; Escaped ellipsis
'(x (... ...))]))]))
(define-quoter my-quote)
(my-quote a b c) ; (a b c)
; Alternative ellipsis (Chez extension)
(define-syntax my-macro
(syntax-rules ::: () ; Use ::: instead of ...
[(_ a :::)
(list a :::)]))
(my-macro 1 2 3) ; (1 2 3)
Hygiene and syntax-rules:
; Syntax-rules is automatically hygienic
(define-syntax swap!
(syntax-rules ()
[(_ a b)
(let ([temp a]) ; temp is hygienic
(set! a b)
(set! b temp))]))
(let ([temp 1] [x 2])
(swap! temp x) ; Works! temp in macro is different
(list temp x)) ; (2 1)
; Introduced identifiers are renamed
(define-syntax capture-free
(syntax-rules ()
[(_ x e)
(let ([x 'captured]) ; This x is hygienic
e)]))
(let ([y 1])
(capture-free y (list y))) ; (1) - y not captured
; Use syntax-case for intentional capture
Syntax-Case Transformers
Basic syntax-case:
; syntax-case provides more control than syntax-rules
(define-syntax my-when
(lambda (stx)
(syntax-case stx ()
[(_ test body ...)
#'(if test (begin body ...))])))
; Explicit syntax object construction
(define-syntax my-or
(lambda (stx)
(syntax-case stx ()
[(_) #'#f]
[(_ e) #'e]
[(_ e1 e2 ...)
#'(let ([t e1])
(if t t (my-or e2 ...)))])))
; Access to raw syntax objects
(define-syntax show-syntax
(lambda (stx)
(syntax-case stx ()
[(_ x)
(begin
(printf "Syntax: ~s~n" (syntax->datum #'x))
#'(quote x))])))
; Modules can export syntax
(module utility-macros (when-let unless-let)
(define-syntax when-let
(syntax-rules ()
[(_ ([var expr]) body ...)
(let ([var expr])
(when var body ...))]))
(define-syntax unless-let
(syntax-rules ()
[(_ ([var expr]) body ...)
(let ([var expr])
(unless var body ...))])))
(import utility-macros)
(when-let ([x (assq 'a '((a . 1)))])
(display (cdr x))) ; 1
; Mixed exports (values and syntax)
(module mixed (value-export syntax-export)
(define value-export 42)
(define-syntax syntax-export
(syntax-rules ()
[(_ x) (list 'syntax x)])))
Standalone Import and Export Forms
Top-level import:
; Import at top level or in bodies
(import (rnrs)) ; Import library
(import my-module) ; Import module
; Import forms
(import (only module-name id ...))
(import (except module-name id ...))
(import (rename module-name (old new) ...))
(import (prefix module-name prefix))
; Multiple imports
(import (rnrs) (rnrs lists) (chezscheme))
; Combining import modifiers
(import (prefix (only (rnrs lists) filter map) list:))
; Now use list:filter, list:map
; Import into local scope
(let ()
(import (only (rnrs) display))
(display "hello")) ; Only display is imported here
Indirect exports:
; indirect-export makes helper visible to macros
(module m (transform)
(indirect-export transform helper)
(define (helper x)
(* x 2))
(define-syntax transform
(syntax-rules ()
[(_ x) (helper x)])))
(import m)
(transform 5) ; 10
; helper is available because of indirect-export
; Without indirect-export, helper would not be
; visible when transform expands in importing context
; Multiple indirect exports
(module m2 (my-macro)
(indirect-export my-macro h1 h2 h3)
(define (h1 x) ...)
(define (h2 x) ...)
(define (h3 x) ...)
(define-syntax my-macro ...))
Export forms in modules:
; Explicit export list in module form
(module name (export1 export2)
...)
; Export everything defined
(module name *
...) ; All definitions exported
; Selective re-export
(module wrapper (public-fn)
(import internal-module)
; Only re-export public-fn from internal-module
(define public-fn internal-fn))
; Renaming exports
(module m ((rename internal-name external-name))
(define (internal-name x) ...))
; After import, use external-name
Import and export interaction:
; Import for re-export
(module aggregator (a b c d)
(import module-1) ; Provides a, b
(import module-2) ; Provides c, d
; All four are now exported from aggregator
)
; Import with aliases for internal use
(module m (process)
(import (rename other-module
(long-name short)))
(define (process x)
(short x))) ; Use short name internally
; Shadowing imports
(module m (my-map)
(import (except (rnrs) map)) ; Don't import map
(define (my-map f lst) ; Define our own
...))
Built-in Modules
Scheme module:
; The scheme module contains core bindings
(import scheme)
; Equivalent to importing base language features
; Includes:
; - Core syntax: lambda, if, define, let, etc.
; - Basic procedures: car, cdr, cons, list, etc.
; - Numeric operations: +, -, *, /, etc.
; - I/O: read, write, display, etc.
; Typically you'd use libraries instead:
(import (rnrs)) ; R6RS standard
(import (chezscheme)) ; Full Chez Scheme
Chez Scheme built-in module:
; $system module contains internal procedures
; Use with caution - implementation details
; Access to internal representations
#%$procedure-name ; Get procedure name
#%$code-source ; Get code source info
#%$continuation? ; Check for continuation
; Example (implementation-specific):
(define (procedure-info proc)
(and (procedure? proc)
(#%$procedure-name proc)))
; These are not part of the public API and
; may change between Chez Scheme versions
ieee and r5rs modules:
; ieee module for IEEE Scheme compatibility
(import ieee)
; r5rs module for R5RS compatibility
(import (rnrs r5rs))
; Contains R5RS-specific forms:
; - null-environment
; - scheme-report-environment
; - delay, force
; - load
; - etc.
; Use for compatibility with older code
(let ([env (scheme-report-environment 5)])
(eval '(+ 1 2) env)) ; Evaluate in R5RS environment
; Define module for expand-time use
(meta module syntax-helpers (make-id with-ids)
(define (make-id template sym)
(datum->syntax template sym))
(define (with-ids template syms proc)
(apply proc (map (lambda (s) (make-id template s)) syms))))
; Use in macro
(meta import syntax-helpers)
(define-syntax define-accessors
(lambda (stx)
(syntax-case stx ()
[(_ name field ...)
(with-ids #'name
'(getter setter)
(lambda (get set)
#`(begin
(define (#,get obj) ...)
(define (#,set obj val) ...))))])))
Phases and meta levels:
; Meta levels:
; Level 0: run time
; Level 1: expand time (macro execution)
; Level 2: meta-expand time (macros defining macros)
; Regular definition (level 0)
(define run-time-value 42)
; Meta definition (level 1)
(meta define expand-time-value 100)
; Use expand-time value in macro
(define-syntax use-expand-time
(lambda (stx)
#`'#,expand-time-value))
(use-expand-time) ; 100
; Nested meta for level 2
(meta meta define level-2-value 'deep)
; For library imports at different phases:
(import (for (helper-lib) expand)) ; Import for expand time
Conditional Expansion
cond-expand form:
; cond-expand chooses based on features
(cond-expand
[chez-scheme
(define implementation 'chez)]
[guile
(define implementation 'guile)]
[else
(define implementation 'unknown)])
; Feature requirements
(cond-expand
[(and srfi-1 srfi-13)
; Both available
(import (srfi :1) (srfi :13))]
[(or srfi-1 rnrs-lists)
; At least one list library
(import (rnrs lists))]
[else
(error 'init "need list library")])
; Check for library existence
(cond-expand
[(library (my optional-lib))
(import (my optional-lib))]
[else
(define optional-feature #f)])
; Preserve source info in macro expansion
(define-syntax my-define
(lambda (stx)
(syntax-case stx ()
[(_ name expr)
; Copy annotation from original to expansion
(let ([anno (syntax->annotation #'expr)])
(if anno
; Preserve source location
#`(define name #,(syntax-with-annotation
#'expr anno))
#'(define name expr)))])))
; Helpful for error messages
(define-syntax check-positive
(lambda (stx)
(syntax-case stx ()
[(_ expr)
#`(let ([v expr])
(unless (positive? v)
(error 'check-positive
(format "~a: expected positive, got ~a"
'#,(syntax->source-info #'expr) v)))
v)])))
; syntax->source-info extracts location string
Annotation options:
; Control annotation generation
(generate-inspector-information) ; Get current
(generate-inspector-information #t) ; Enable
(generate-procedure-source-information)
(generate-procedure-source-information #t)
; When enabled, compiled code contains source info
; for better error messages and debugging
; Strip annotations for production
(strip-fasl-file "debug.so" "release.so")
; Check if datum has annotation
(annotation? obj)
; Get annotation from various forms
(syntax->annotation stx) ; From syntax object
(record-type-descriptor-uid-source-info rtd) ; From RTD
Source Tables
Source table basics:
; Source tables map positions to source info
; Used by debugger and profiler
; Make source table during compilation
(parameterize ([generate-inspector-information #t])
(compile-file "source.ss"))
; Source tables are embedded in compiled files
; and used for:
; - Stack traces
; - Debugging
; - Profiling
; - Code coverage
; Get source from procedure
(define (procedure-source-info proc)
(#%$code-source (#%$closure-code proc)))
; Returns source position if available
; Inspector uses source tables for display
; Enabled with generate-inspector-information
(debug-on-exception #t)
; When error occurs, inspector shows:
; - Source file
; - Line and column
; - Expression
; Manual inspection
(inspect object)
; Shows source if available
; Type 'e' to see expression
; Type 's' to see source location
; Continuation inspector
(call/cc
(lambda (k)
(inspect-continuation k)))
; Shows call stack with source info
Building and using source tables:
; Compile with source information
(parameterize ([generate-inspector-information #t]
[generate-procedure-source-information #t])
(compile-file "mycode.ss"))
; Source information increases .so size
; but enables better debugging
; Verify source info present
(define (has-source-info? proc)
(and (procedure? proc)
(#%$code-source (#%$closure-code proc))))
; Extract source for display
(define (show-procedure-source proc)
(let ([src (has-source-info? proc)])
(if src
(printf "Source: ~a~n" src)
(printf "No source information~n"))))
; Source tables for read syntax
(define (read-with-source-table filename)
(call-with-input-file filename
(lambda (port)
(let ([sfd (make-source-file-descriptor filename port)])
(let loop ([datums '()])
(let ([d (get-datum/annotations port sfd)])
(if (eof-object? d)
(reverse datums)
(loop (cons d datums)))))))))
Source table utilities:
; Walk through source table entries
(define (enumerate-sources compiled-code)
; Implementation-specific access to source table
; Typically used internally by profiler/debugger
...)
; Convert position to line/column
(define (position->line-col filename pos)
(call-with-input-file filename
(lambda (port)
(let loop ([line 1] [col 0] [p 0])
(if (= p pos)
(values line col)
(let ([c (read-char port)])
(if (eof-object? c)
(values line col)
(if (char=? c #\newline)
(loop (+ line 1) 0 (+ p 1))
(loop line (+ col 1) (+ p 1))))))))))
; Format source location for display
(define (format-source-location sfd bfp)
(let ([filename (source-file-descriptor-path sfd)])
(let-values ([(line col) (position->line-col filename bfp)])
(format "~a:~a:~a" filename line col))))
Complete Macro System Example
Pattern matching macro:
; Comprehensive match macro using syntax-case
(define-syntax match
(lambda (stx)
(syntax-case stx ()
[(_ expr clause ...)
#'(let ([v expr])
(match-clauses v clause ...))])))
(define-syntax match-clauses
(lambda (stx)
(syntax-case stx (else)
[(_ v)
#'(error 'match "no matching clause")]
[(_ v (else body ...))
#'(begin body ...)]
[(_ v (pattern body ...) rest ...)
#'(let ([fail (lambda () (match-clauses v rest ...))])
(match-pattern v pattern (begin body ...) (fail)))])))
(define-syntax match-pattern
(lambda (stx)
(syntax-case stx (quote ?)
; Wildcard
[(_ v _ success fail)
#'success]
; Variable binding
[(_ v name success fail)
(identifier? #'name)
#'(let ([name v]) success)]
; Literal quote
[(_ v 'datum success fail)
#'(if (equal? v 'datum) success fail)]
; Predicate check
[(_ v (? pred) success fail)
#'(if (pred v) success fail)]
; Predicate with binding
[(_ v (? pred name) success fail)
(identifier? #'name)
#'(if (pred v)
(let ([name v]) success)
fail)]
; Pair pattern
[(_ v (pat1 . pat2) success fail)
#'(if (pair? v)
(match-pattern (car v) pat1
(match-pattern (cdr v) pat2 success fail)
fail)
fail)]
; Vector pattern
[(_ v #(pat ...) success fail)
#'(if (and (vector? v) (= (vector-length v) (length '(pat ...))))
(match-vector v 0 (pat ...) success fail)
fail)]
; Literal
[(_ v literal success fail)
#'(if (equal? v 'literal) success fail)])))
(define-syntax match-vector
(syntax-rules ()
[(_ v i () success fail) success]
[(_ v i (pat rest ...) success fail)
(match-pattern (vector-ref v i) pat
(match-vector v (+ i 1) (rest ...) success fail)
fail)]))
; Usage examples
(define (eval-expr expr env)
(match expr
[(? number?) expr]
[(? symbol? name) (lookup name env)]
[('quote datum) datum]
[('if test then else)
(if (eval-expr test env)
(eval-expr then env)
(eval-expr else env))]
[('lambda (params ...) body)
(lambda args
(eval-expr body (extend-env params args env)))]
[(proc args ...)
(apply (eval-expr proc env)
(map (lambda (a) (eval-expr a env)) args))]
[else (error 'eval "unknown expression" expr)]))
; with-exception-handler for low-level handling
(with-exception-handler
(lambda (condition)
(display "Exception caught\n")
(display condition)
(newline))
(lambda ()
(raise 'my-exception)))
; Handler must handle or re-raise
(with-exception-handler
(lambda (c)
(if (warning? c)
(begin
(display "Ignoring warning\n")
42) ; Return value for continuable
(raise c))) ; Re-raise others
(lambda ()
(raise-continuable (make-warning))))
; Nested handlers
(with-exception-handler
(lambda (c) (printf "Outer: ~a~n" c))
(lambda ()
(with-exception-handler
(lambda (c)
(printf "Inner: ~a~n" c)
(raise c)) ; Pass to outer
(lambda ()
(raise 'test)))))
Condition types:
; R6RS standard conditions
(make-error) ; General error
(make-assertion-violation) ; Assertion failure
(make-violation) ; Contract violation
(make-warning) ; Warning
(make-serious-condition) ; Serious condition
(make-irritants-condition '(1 2 3))
(make-who-condition 'my-proc)
(make-message-condition "error message")
; Compound conditions
(condition
(make-error)
(make-who-condition 'divide)
(make-message-condition "division by zero")
(make-irritants-condition '(0)))
; Condition predicates
(error? c)
(warning? c)
(serious-condition? c)
(violation? c)
(assertion-violation? c)
(irritants-condition? c)
(who-condition? c)
(message-condition? c)
; Condition accessors
(condition-who c) ; Get who
(condition-message c) ; Get message
(condition-irritants c) ; Get irritants
; Enter debugger on unhandled exceptions
(debug-on-exception #t)
; When enabled, unhandled exceptions enter inspector
; Type ? for help in inspector
; Reset handler (Chez extension)
(reset-handler) ; Get current
(reset-handler (lambda () (exit 1))) ; Set
; Abort handler
(abort-handler) ; Get current
(abort-handler (lambda () (reset)))
; Base exception handler
(base-exception-handler)
(base-exception-handler
(lambda (c)
(display-condition c)
(reset)))
; Exit on exception (useful for scripts)
(parameterize ([reset-handler exit]
[abort-handler exit])
(run-main-program))
; time measures expression evaluation
(time (factorial 10000))
; (time (factorial 10000))
; 2 collections
; 32 ms elapsed cpu time, including 5 ms collecting
; 35 ms elapsed real time
; 5,234,567 bytes allocated
; Returns the expression result
(define result (time (heavy-computation)))
; time reports:
; - Number of GC collections
; - CPU time (including GC)
; - Real (wall clock) time
; - Bytes allocated
Statistics procedures:
; statistics returns detailed info
(statistics)
; Returns:
; ((time type . value) ...)
; Specific statistics
(cpu-time) ; CPU time in nanoseconds
(real-time) ; Real time in nanoseconds
(bytes-allocated) ; Total bytes allocated
(current-memory-bytes) ; Current memory usage
; Collections
(collections) ; Number of GC collections
(collect-trip-bytes) ; Bytes between collections
; More detailed
(sstats-cpu sstats) ; CPU time from sstats
(sstats-real sstats) ; Real time
(sstats-bytes sstats) ; Bytes allocated
(sstats-gc-count sstats) ; GC count
(sstats-gc-cpu sstats) ; GC CPU time
(sstats-gc-real sstats) ; GC real time
(sstats-gc-bytes sstats) ; Bytes reclaimed
; Parameters are thread-local
(define my-param (make-parameter 0))
(my-param 1) ; Set in main thread
(fork-thread
(lambda ()
(my-param) ; 1 (inherited)
(my-param 2) ; Set in child
(my-param))) ; 2
(my-param) ; Still 1 in main thread
; parameterize in thread
(fork-thread
(lambda ()
(parameterize ([my-param 100])
; my-param is 100 in this thread
(work))))
; Initial values for new threads
(define (fork-thread/params thunk . param-vals)
(fork-thread
(lambda ()
(apply parameterize param-vals
thunk))))
Virtual Registers
Virtual register basics:
; Virtual registers are fast thread-local storage
; Faster than parameters, no guard support
; Number of available registers
(virtual-register-count) ; Implementation-dependent
; Access virtual register
(virtual-register 0) ; Get register 0
(set-virtual-register! 0 'value) ; Set register 0
; Example usage
(set-virtual-register! 0 0) ; Counter
(set-virtual-register! 1 '()) ; Accumulator
(define (fast-count!)
(set-virtual-register! 0 (+ (virtual-register 0) 1)))
(define (fast-push! v)
(set-virtual-register! 1 (cons v (virtual-register 1))))
Virtual registers vs parameters:
; Parameters: safer, guard support, slightly slower
(define my-param (make-parameter 0))
(my-param) ; Read
(my-param 1) ; Write
; Virtual registers: faster, no guards, raw access
(virtual-register 0) ; Read
(set-virtual-register! 0 1) ; Write
; Use virtual registers for:
; - Performance-critical inner loops
; - Simple values (no validation needed)
; - Temporary storage
; Use parameters for:
; - Configuration settings
; - Validated/transformed values
; - External interfaces
; Benchmark comparison
(time
(do ([i 0 (+ i 1)])
((= i 1000000))
(my-param (+ (my-param) 1))))
(time
(do ([i 0 (+ i 1)])
((= i 1000000))
(set-virtual-register! 0
(+ (virtual-register 0) 1))))
Thread safety:
; Virtual registers are thread-local
(set-virtual-register! 0 'main)
(fork-thread
(lambda ()
(set-virtual-register! 0 'child)
(virtual-register 0))) ; 'child
(virtual-register 0) ; 'main (unchanged)
; Each thread has own set of virtual registers
; No synchronization needed
; But be careful: values are not automatically copied
; to new threads - they start uninitialized
(define (init-thread-registers!)
(set-virtual-register! 0 0)
(set-virtual-register! 1 '())
(set-virtual-register! 2 (make-eq-hashtable)))
Environmental Queries and Settings
System information:
; Scheme version
(scheme-version) ; "9.5.8"
(scheme-version-number) ; #(9 5 8)
(petite?) ; #t if Petite Chez Scheme
; Machine information
(machine-type) ; 'a6osx, 'ta6le, 'arm64le, etc.
(threaded?) ; #t if threaded build
(native-code) ; #t if native code generation
; Endianness
(native-endianness) ; 'little or 'big
; Chez Scheme directory
(scheme-directory) ; Installation directory
; Features
(machine-features) ; List of features
; (pthreads ieee-floating-point ...)
Environment variables:
; Get environment variable
(getenv "HOME") ; "/home/user"
(getenv "UNDEFINED") ; #f if not set
; Set environment variable (Chez extension)
(putenv "MY_VAR" "value")
(getenv "MY_VAR") ; "value"
; Unset
(putenv "MY_VAR" #f) ; Remove variable
; Common environment variables
(getenv "PATH")
(getenv "USER")
(getenv "SHELL")
(getenv "TERM")
(getenv "SCHEMEHEAPDIRS") ; Chez Scheme heap location
; Get all environment variables
; Not directly available, but can parse from (system "env")
Command line and process:
; Command line arguments
(command-line) ; All args including program name
(command-line-arguments) ; Just arguments
; Process ID
(getpid) ; Current process ID
; Exit program
(exit) ; Exit with status 0
(exit 1) ; Exit with status 1
; Scheme program/script name
(car (command-line)) ; Program name
; Working directory
(current-directory) ; Get
(current-directory "/tmp") ; Set
(cd "/tmp") ; Alias
; Host name
(system "hostname") ; Via shell
; or use foreign function interface
Memory and heap:
; Heap information
(bytes-allocated) ; Total allocated
(current-memory-bytes) ; Current heap size
; Heap directories (for boot files)
(scheme-heap-path) ; Where to find heaps
; Memory limit (if supported)
; Set via --heap-limit command line
; Object counts (when enabled)
(object-counts) ; Association list of types and counts
; Enable allocation counting
(generate-allocation-counts #t)
; Weak references and ephemerons
; Interact with GC - see earlier chapters
; Finalization
(collect-rendezvous) ; Wait for finalizers
Subset Modes
Subset mode basics:
; subset-mode controls language dialect
(subset-mode) ; Get current mode
; Available modes:
; 'system - Full Chez Scheme (default)
; 'r6rs - Strict R6RS compliance
; Set mode
(subset-mode 'r6rs)
; In r6rs mode:
; - Only R6RS syntax/procedures available
; - Chez extensions disabled
; - Stricter error checking
; Reset to full mode
(subset-mode 'system)
; Check mode
(eq? (subset-mode) 'r6rs) ; #t if in R6RS mode
R6RS compliance:
; R6RS mode disables:
; - printf, format extensions
; - trace, inspect
; - Chez-specific syntax
; - Extended reader syntax
; Example differences
(subset-mode 'system)
(printf "~a~n" 42) ; Works
(subset-mode 'r6rs)
; (printf "~a~n" 42) ; Error: printf not bound
; Library differences
; R6RS mode only allows R6RS libraries
(import (rnrs)) ; OK in both modes
; (import (chezscheme)) ; Error in R6RS mode
; Use for testing portability
(define (test-r6rs-compatible)
(parameterize ([subset-mode 'r6rs])
(load "my-library.ss")))
IEEE mode:
; ieee-mode for IEEE Scheme compliance
(ieee-mode) ; Get current
; Differences in IEEE mode:
; - Implicit forcing of promises
; - Different treatment of '() and #f
; - Stricter type checking
; Historical compatibility
; Most code doesn't need this
; Combining modes
(subset-mode 'system) ; Full Chez
(ieee-mode #f) ; No IEEE restrictions
Mode-specific code:
; Conditional on mode
(define (portable-print x)
(if (eq? (subset-mode) 'r6rs)
(begin (display x) (newline))
(printf "~a~n" x)))
; Provide fallbacks
(define my-printf
(if (top-level-bound? 'printf)
printf
(lambda (fmt . args)
(display (apply format fmt args))
(newline))))
; Library that works in both modes
(library (my portable-lib)
(export my-proc)
(import (rnrs)) ; Only R6RS imports
; Only R6RS forms in body
(define (my-proc x)
(let ([y (* x 2)])
y)))
; Test in both modes
(define (test-both-modes thunk)
(let ([system-result
(parameterize ([subset-mode 'system])
(thunk))]
[r6rs-result
(parameterize ([subset-mode 'r6rs])
(thunk))])
(list system-result r6rs-result)))
; Chez Scheme uses generational garbage collection
; Objects are allocated in generation 0 (youngest)
; Surviving objects are promoted to older generations
; Generation hierarchy:
; - Generation 0: New objects (collected frequently)
; - Generation 1: Survived one collection
; - ...
; - Generation N: Oldest (collected rarely)
; Check maximum generation
(collect-maximum-generation) ; Returns highest generation number
; Typical values: 4 or 5 generations
; Generation 0 = nursery (very fast allocation)
Generational collection visualization:
Manual garbage collection:
; collect triggers garbage collection
(collect) ; Full collection (all generations)
(collect 0) ; Collect generation 0 only
(collect 1) ; Collect generations 0 and 1
(collect 2) ; Collect generations 0, 1, and 2
; Collect specific generation
(collect (collect-maximum-generation)) ; Full collection
; Example: force cleanup before measurement
(collect)
(collect) ; Run twice to ensure finalization
(let ([mem-before (current-memory-bytes)])
(create-large-structure)
(collect)
(let ([mem-after (current-memory-bytes)])
(printf "Memory used: ~a bytes~n"
(- mem-after mem-before))))
GC parameters:
; collect-trip-bytes: bytes allocated before auto-collect
(collect-trip-bytes) ; Get current value
(collect-trip-bytes 1000000) ; Set to 1MB
; Larger value = less frequent GC, more memory use
; Smaller value = more frequent GC, less memory use
; collect-generation-radix: size ratio between generations
(collect-generation-radix) ; Typically 4
; Each generation is ~4x larger than previous
; collect-maximum-generation: highest generation number
(collect-maximum-generation) ; Typically 4 or 5
; release-minimum-generation: when to release memory to OS
(release-minimum-generation)
(release-minimum-generation 2) ; Release after gen 2+ collection
; heap-reserve-ratio: extra heap space to reserve
(heap-reserve-ratio) ; Default 1.0 (100% extra)
; Weak pairs hold car weakly - GC can collect it
(define wp (weak-cons 'key 'value))
; Access like normal pair
(car wp) ; 'key
(cdr wp) ; 'value
; If car is only referenced through weak pairs,
; it can be collected and replaced with #!bwp
; Check for broken weak pointer
(bwp-object? (car wp)) ; #f while key exists
; After key is collected:
(bwp-object? (car wp)) ; #t
; Example: weak cache
(define cache '())
(define (cache-put! key value)
(set! cache (cons (weak-cons key value) cache)))
(define (cache-get key)
(let loop ([pairs cache])
(cond
[(null? pairs) #f]
[(bwp-object? (caar pairs))
; Clean up broken entries
(loop (cdr pairs))]
[(eq? (caar pairs) key)
(cdar pairs)]
[else (loop (cdr pairs))])))
; Clean broken weak pairs periodically
(define (cache-clean!)
(set! cache
(filter (lambda (wp)
(not (bwp-object? (car wp))))
cache)))
Weak pair visualization:
Ephemeron pairs:
; Ephemerons: car is key, cdr kept alive only if car is alive
(define ep (ephemeron-cons 'key 'value))
; Similar to weak-cons but with important difference:
; - weak-cons: cdr always kept alive
; - ephemeron-cons: cdr collected when car is collected
; This prevents memory leaks in certain scenarios
(car ep) ; 'key
(cdr ep) ; 'value
; Check if broken
(bwp-object? (car ep)) ; #f while key exists
; Use case: associating data with objects without preventing GC
(define object-metadata (make-eq-hashtable))
; Problem with weak-cons in hashtable:
; Key can be collected but value stays (memory leak)
; Solution: use ephemeron hashtable
(define object-metadata (make-ephemeron-eq-hashtable))
; When key is collected, value is also eligible for collection
Ephemeron vs weak pair comparison:
Guardians:
; Guardians track when objects become unreachable
(define g (make-guardian))
; Register object with guardian
(let ([obj (list 1 2 3)])
(g obj) ; Register
; obj is now guarded
)
; After GC, check for collected objects
(collect)
(g) ; Returns collected object or #f
; Multiple registrations
(define g (make-guardian))
(g (cons 'a 'b))
(g (vector 1 2 3))
(g "temporary string")
(collect)
; Retrieve all collected objects
(let loop ()
(let ([obj (g)])
(when obj
(printf "Collected: ~a~n" obj)
(loop))))
; Use case: cleanup resources
(define resource-guardian (make-guardian))
(define (make-managed-resource)
(let ([resource (allocate-external-resource)])
(resource-guardian resource)
resource))
; Periodic cleanup
(define (cleanup-resources!)
(let loop ()
(let ([r (resource-guardian)])
(when r
(release-external-resource r)
(loop)))))
; Foreign callables need special consideration
(define callback
(foreign-callable
(lambda (x) (* x 2))
(int)
int))
; Lock the callable code
(lock-object callback)
; Get entry point (safe because locked)
(define callback-ptr (foreign-callable-entry-point callback))
; Pass to C code
(register-callback callback-ptr)
; Keep locked as long as C might call it
; ...
; When done:
(unlock-object callback)
; Helper for managed callbacks
(define active-callbacks '())
(define (make-persistent-callback proc arg-types ret-type)
(let ([cb (foreign-callable proc arg-types ret-type)])
(lock-object cb)
(set! active-callbacks (cons cb active-callbacks))
(foreign-callable-entry-point cb)))
(define (release-all-callbacks!)
(for-each unlock-object active-callbacks)
(set! active-callbacks '()))
Immobile Objects
Understanding immobile objects:
; Some Chez Scheme objects are naturally immobile
; They are allocated in non-moving memory regions
; Immobile by design:
; - Code objects (procedures)
; - Large objects (above certain threshold)
; - Objects in static generation
; Check if object might move
; (No direct predicate, but can infer from type)
; Large object threshold
; Objects above this size go to large object space
; Typically 1KB-4KB depending on configuration
; Large objects don't move but can be collected
(define large-vector (make-vector 10000))
; Allocated in large object space - won't move
; vs small objects that might move
(define small-vector (make-vector 10))
; Allocated in nursery - may move during GC
Memory regions visualization:
Static generation:
; Objects can be made permanent (static generation)
; collect-maximum-generation + 1 = static
(define important-data (list 1 2 3 4 5))
; Promote to oldest generation through collections
(do ([i 0 (+ i 1)])
((= i (+ (collect-maximum-generation) 2)))
(collect i))
; Now object is in oldest generation
; Less likely to be scanned/moved
; Note: There's no direct API to force static allocation
; Objects naturally move to older generations by surviving GC
; For truly permanent data, consider:
; 1. Define at top level (lives in static space)
; 2. Lock the object
; 3. Use foreign allocation
Code objects and immobility:
; Compiled procedures are code objects
(define (my-procedure x) (* x 2))
; Code objects are inherently immobile
; Their addresses are stable
; This is why foreign-callable works
(define my-callback
(foreign-callable
(lambda () (display "Hello\n"))
()
void))
; Code is immobile, but the closure might not be!
; For closures with free variables:
(define (make-adder n)
(lambda (x) (+ x n)))
(define add-5 (make-adder 5))
; The closure object (containing n=5) CAN move
; But the code itself is immobile
; When passing to FFI, lock the closure
(lock-object add-5)
; Now both code and closure environment are stable
Working with immobile data:
; Allocate data that won't move
; Option 1: Use large enough allocation
(define immobile-buffer
(make-bytevector (* 64 1024))) ; 64KB - definitely in LOS
; Option 2: Foreign allocation
(define foreign-buffer
(foreign-alloc 1024)) ; Not managed by GC at all
; Option 3: Lock small objects
(define small-data (make-bytevector 100))
(lock-object small-data)
; Getting stable pointer for FFI
(define (get-stable-pointer obj)
(cond
; Bytevector data pointer
[(bytevector? obj)
(lock-object obj)
(#%$bytevector-data obj)]
; String data pointer
[(string? obj)
(lock-object obj)
(#%$string-data obj)]
[else
(error 'get-stable-pointer "unsupported type")]))
; Remember to unlock when done!
(define (release-stable-pointer obj)
(unlock-object obj))
Phantom Bytevectors
Phantom bytevector basics:
; Phantom bytevectors track external memory for GC accounting
; They don't actually contain data - just report size to GC
; Create phantom bytevector
(define phantom (make-phantom-bytevector 1000000))
; This tells GC: "I have 1MB of external memory"
; GC includes this in its memory pressure calculations
; Use case: foreign-allocated memory
(define external-buffer (foreign-alloc (* 1024 1024))) ; 1MB
(define phantom (make-phantom-bytevector (* 1024 1024)))
; GC now knows about the external memory
; Will collect more aggressively if memory pressure high
; When freeing external memory, release phantom too
(foreign-free external-buffer)
(set! phantom #f) ; Allow phantom to be collected
; The expression editor provides line editing capabilities
; Similar to GNU Readline but integrated with Scheme
; Check if expression editor is enabled
(expression-editor) ; #t if enabled
; Enable expression editor
(expression-editor #t)
; Disable expression editor
(expression-editor #f)
; The expression editor provides:
; - Line editing (insert, delete, cursor movement)
; - History navigation
; - Parenthesis matching
; - Tab completion
; - Multi-line editing
; - Emacs-style key bindings (default)
History parameters:
; ee-history-limit: maximum history entries
(ee-history-limit) ; Get current limit
(ee-history-limit 500) ; Set to 500 entries
; History file location
(ee-history-file) ; Get current file path
(ee-history-file "~/.chez_history") ; Set custom path
; Typical default: ~/.chez_history or platform equivalent
; History is automatically saved/loaded
; Each expression entered is added to history
; Disable history persistence
(ee-history-file #f) ; Don't save history
; Clear current history
; (No direct command - restart or set new file)
Display and formatting parameters:
; ee-paren-flash-delay: parenthesis flash duration
(ee-paren-flash-delay) ; Get current (milliseconds)
(ee-paren-flash-delay 100) ; Set to 100ms
; When you type a closing paren, cursor briefly jumps
; to matching open paren, then returns
; Disable paren flashing
(ee-paren-flash-delay #f)
; ee-default-repeat: default repeat count
(ee-default-repeat) ; Get current
(ee-default-repeat 4) ; Set default repeat
; Used with universal argument (C-u)
; C-u C-f moves forward 4 characters by default
; ee-auto-indent: automatic indentation
(ee-auto-indent) ; #t if enabled
(ee-auto-indent #t) ; Enable auto-indent
; Automatically indents new lines based on
; Scheme syntax (parentheses depth)
Completion parameters:
; ee-common-identifiers: identifiers for completion
(ee-common-identifiers) ; Get current list
; Add custom identifiers for completion
(ee-common-identifiers
(append '(my-special-function my-custom-macro)
(ee-common-identifiers)))
; Completion sources:
; 1. Bound identifiers in current environment
; 2. Common identifiers list
; 3. History entries
; Tab completion behavior:
; - Single match: complete immediately
; - Multiple matches: show options
; - No match: beep or do nothing
; ee-auto-paren-balance: auto-balance parentheses
(ee-auto-paren-balance) ; #t if enabled
(ee-auto-paren-balance #t) ; Enable
; Helps maintain balanced parentheses during editing
Terminal parameters:
; ee-noisy: beep on errors
(ee-noisy) ; #t if beeping enabled
(ee-noisy #f) ; Disable beeping
; Terminal dimensions (usually auto-detected)
; Can affect line wrapping and display
; Console parameters
(console-input-port) ; Input port for editor
(console-output-port) ; Output port for editor
; The expression editor works with terminal capabilities
; TERM environment variable affects behavior
; For best experience:
; - Use terminal with ANSI support
; - Ensure TERM is set correctly
; - UTF-8 encoding recommended
; Check terminal type
(getenv "TERM") ; e.g., "xterm-256color"
Expression editor parameter summary:
Parameter
Type
Description
expression-editor
boolean
Enable/disable expression editor
ee-history-limit
integer
Maximum history entries to keep
ee-history-file
string/#f
Path to history file
ee-paren-flash-delay
integer/#f
Paren matching flash delay (ms)
ee-auto-indent
boolean
Auto-indent new lines
ee-default-repeat
integer
Default repeat count for C-u
ee-noisy
boolean
Beep on errors
ee-auto-paren-balance
boolean
Auto-balance parentheses
ee-common-identifiers
list
Identifiers for tab completion
Key Binding
Understanding key representations:
; Keys are represented as strings with special notation
; Regular characters
"a" ; The letter 'a'
"A" ; The letter 'A' (shift-a)
"1" ; The digit '1'
" " ; Space
; Control characters (C- prefix)
"\\C-a" ; Control-a (^A)
"\\C-b" ; Control-b (^B)
"\\C-x" ; Control-x (^X)
; Meta/Alt characters (M- or \\e prefix)
"\\M-f" ; Meta-f (Alt-f or Esc f)
"\\ef" ; Same as above (escape followed by f)
"\\M-b" ; Meta-b
; Special keys
"\\C-m" ; Return/Enter (same as \\r)
"\\C-i" ; Tab (same as \\t)
"\\C-j" ; Newline
"\\C-[" ; Escape
; Key sequences (multiple keys)
"\\C-x\\C-f" ; Control-x followed by Control-f
"\\C-xb" ; Control-x followed by 'b'
"\\exf" ; Escape, then 'x', then 'f'
Key binding visualization:
Viewing current bindings:
; ee-binding-list: get all current bindings
(ee-binding-list)
; Returns association list of (key . command) pairs
; Example output:
; (("\\C-a" . ee-beginning-of-line)
; ("\\C-e" . ee-end-of-line)
; ("\\C-f" . ee-forward-char)
; ("\\C-b" . ee-backward-char)
; ...)
; Find binding for specific key
(define (find-binding key)
(let ([bindings (ee-binding-list)])
(cond
[(assoc key bindings) => cdr]
[else #f])))
(find-binding "\\C-a") ; ee-beginning-of-line
; Find key for specific command
(define (find-key-for-command cmd)
(let loop ([bindings (ee-binding-list)])
(cond
[(null? bindings) #f]
[(eq? (cdar bindings) cmd) (caar bindings)]
[else (loop (cdr bindings))])))
(find-key-for-command 'ee-beginning-of-line) ; "\\C-a"
Setting key bindings:
; ee-bind-key: bind key to command
(ee-bind-key "\\C-t" 'ee-transpose-char)
(ee-bind-key "\\M-u" 'ee-upcase-word)
(ee-bind-key "\\M-l" 'ee-downcase-word)
; Bind key sequence
(ee-bind-key "\\C-x\\C-s" 'ee-save-history)
(ee-bind-key "\\C-xh" 'ee-select-all)
; Unbind a key (bind to #f)
(ee-bind-key "\\C-z" #f) ; Disable Ctrl-Z
; Rebind existing key
(ee-bind-key "\\C-w" 'ee-backward-kill-word) ; Change from default
; Multiple bindings for same command
(ee-bind-key "\\C-a" 'ee-beginning-of-line)
(ee-bind-key "\\eOH" 'ee-beginning-of-line) ; Home key (some terminals)
; Bind to self-insert for special characters
(ee-bind-key "λ" 'ee-self-insert)
Default Emacs-style bindings:
Key
Command
Description
C-a
ee-beginning-of-line
Move to beginning of line
C-e
ee-end-of-line
Move to end of line
C-f
ee-forward-char
Move forward one character
C-b
ee-backward-char
Move backward one character
M-f
ee-forward-word
Move forward one word
M-b
ee-backward-word
Move backward one word
C-p
ee-previous-history
Previous history entry
C-n
ee-next-history
Next history entry
C-d
ee-delete-char
Delete character under cursor
C-h / DEL
ee-backward-delete-char
Delete character before cursor
C-k
ee-kill-line
Kill to end of line
C-y
ee-yank
Yank (paste) killed text
C-_
ee-undo
Undo last change
C-r
ee-history-search-backward
Search history backward
C-s
ee-history-search-forward
Search history forward
TAB
ee-complete
Complete identifier
C-m / RET
ee-accept
Accept input (if complete)
C-j
ee-newline
Insert newline
C-g
ee-abort
Abort current input
C-l
ee-redisplay
Redraw screen
Custom key binding configuration:
; Setup function for custom bindings
(define (setup-my-keybindings)
; Vi-like bindings (example)
; Note: This is partial - full vi mode would need more
; Navigation in "normal" style
(ee-bind-key "\\C-h" 'ee-backward-char) ; h = left
(ee-bind-key "\\C-l" 'ee-forward-char) ; l = right
(ee-bind-key "\\ew" 'ee-forward-word) ; w = word forward
(ee-bind-key "\\eb" 'ee-backward-word) ; b = word backward
; Additional useful bindings
(ee-bind-key "\\C-x\\C-e" 'ee-edit-externally)
(ee-bind-key "\\M-p" 'ee-previous-history)
(ee-bind-key "\\M-n" 'ee-next-history)
; Function key bindings (terminal-dependent)
(ee-bind-key "\\e[A" 'ee-previous-history) ; Up arrow
(ee-bind-key "\\e[B" 'ee-next-history) ; Down arrow
(ee-bind-key "\\e[C" 'ee-forward-char) ; Right arrow
(ee-bind-key "\\e[D" 'ee-backward-char) ; Left arrow
(ee-bind-key "\\e[H" 'ee-beginning-of-line) ; Home
(ee-bind-key "\\e[F" 'ee-end-of-line) ; End
(ee-bind-key "\\e[3~" 'ee-delete-char)) ; Delete
; Call during startup (e.g., in .schemerc)
(when (expression-editor)
(setup-my-keybindings))
Editing Commands
Cursor movement commands:
; Character movement
ee-forward-char ; Move right one character (C-f)
ee-backward-char ; Move left one character (C-b)
; Word movement
ee-forward-word ; Move to end of word (M-f)
ee-backward-word ; Move to beginning of word (M-b)
; Line movement
ee-beginning-of-line ; Move to line start (C-a)
ee-end-of-line ; Move to line end (C-e)
ee-previous-line ; Move to previous line (C-p in multi-line)
ee-next-line ; Move to next line (C-n in multi-line)
; Expression movement (Scheme-aware)
ee-forward-exp ; Move forward one S-expression (C-M-f)
ee-backward-exp ; Move backward one S-expression (C-M-b)
; Buffer movement
ee-beginning-of-entry ; Move to start of entire input
ee-end-of-entry ; Move to end of entire input
; Matching parenthesis
ee-flash-matching-paren ; Flash matching paren
ee-goto-matching-paren ; Move to matching paren
Deletion commands:
; Character deletion
ee-delete-char ; Delete char at cursor (C-d)
ee-backward-delete-char ; Delete char before cursor (Backspace)
; Word deletion
ee-delete-word ; Delete word forward (M-d)
ee-backward-delete-word ; Delete word backward (M-Backspace)
; Kill commands (save to kill ring)
ee-kill-line ; Kill to end of line (C-k)
ee-backward-kill-line ; Kill to beginning of line (C-u)
ee-kill-word ; Kill word forward (M-d)
ee-backward-kill-word ; Kill word backward (M-DEL)
ee-kill-exp ; Kill S-expression forward (C-M-k)
ee-backward-kill-exp ; Kill S-expression backward
; Delete entire entry
ee-delete-entry ; Delete entire input
; Note: Kill commands save text to kill ring for yanking
; Delete commands do not save to kill ring
Text insertion and manipulation:
; Self-insert
ee-self-insert ; Insert the typed character
; Newline and indentation
ee-newline ; Insert newline (C-j)
ee-newline-and-indent ; Newline with auto-indent (C-m in some configs)
ee-indent ; Indent current line
ee-indent-all ; Indent entire entry
; Case conversion
ee-upcase-word ; Convert word to uppercase (M-u)
ee-downcase-word ; Convert word to lowercase (M-l)
ee-capitalize-word ; Capitalize word (M-c)
; Transposition
ee-transpose-char ; Swap chars around cursor (C-t)
ee-transpose-word ; Swap words around cursor (M-t)
ee-transpose-exp ; Swap S-expressions (C-M-t)
; Open line
ee-open-line ; Insert newline after cursor (C-o)
Kill ring and yank:
; The kill ring stores killed text for later retrieval
; Yank (paste) most recent kill
ee-yank ; Paste last killed text (C-y)
; Yank previous kills
ee-yank-pop ; Cycle through kill ring (M-y after C-y)
; Kill ring operations:
; 1. C-k kills to end of line, saves to ring
; 2. C-y yanks most recent
; 3. M-y after C-y cycles through older kills
; Example workflow:
; Type: (define x 10)
; C-a C-k ; Kill entire line
; Type: (define y 20)
; C-a C-k ; Kill this line too
; C-y ; Yank "(define y 20)"
; M-y ; Replace with "(define x 10)"
; M-y ; Cycle back to "(define y 20)"
History commands:
; History navigation
ee-previous-history ; Previous entry (C-p or Up)
ee-next-history ; Next entry (C-n or Down)
ee-history-beginning ; First history entry (M-<)
ee-history-end ; Last/current entry (M->)
; History search
ee-history-search-backward ; Search backward (C-r)
ee-history-search-forward ; Search forward (C-s)
; Search behavior:
; 1. Press C-r
; 2. Type search string
; 3. Press C-r again to find next match
; 4. Press Enter to accept
; 5. Press C-g to cancel
; History by prefix
ee-history-bwd-prefix ; Find previous with same prefix
ee-history-fwd-prefix ; Find next with same prefix
; Example: type "(def" then use prefix search
; to find all entries starting with "(def"
Completion commands:
; Tab completion
ee-complete ; Complete identifier (Tab)
ee-complete-list ; Show all completions
; Completion behavior:
; - Completes based on current prefix
; - Sources: bound identifiers, common-identifiers, history
; Example:
; Type: str (then Tab)
; Shows: string->list, string->number, string-append, ...
; Type: string-a (then Tab)
; Completes to: string-append (if unique match)
; Completion is context-aware:
; - At start of expression: all identifiers
; - After open paren: procedure names
; - After quote: symbols
Parenthesis handling:
; Parenthesis matching and manipulation
ee-flash-matching-paren ; Briefly show matching paren
ee-goto-matching-paren ; Move to matching paren
; Insert with matching
ee-insert-paren ; Insert ( and potentially )
ee-insert-bracket ; Insert [ and potentially ]
; Balance checking
ee-paren-balance ; Check if parens are balanced
; The expression editor understands Scheme syntax:
; - () round parentheses
; - [] square brackets
; - {} curly braces (for some syntaxes)
; - "" string delimiters
; - ; comments
; - #| |# block comments
; Accept behavior depends on balance:
; - Balanced: Enter accepts and evaluates
; - Unbalanced: Enter adds newline for continuation
Control and miscellaneous commands:
; Accept and evaluate
ee-accept ; Accept input if complete (Enter/C-m)
ee-newline ; Always insert newline (C-j)
; Abort and reset
ee-abort ; Abort current input (C-g)
ee-reset ; Reset editor state
; Undo
ee-undo ; Undo last edit (C-_ or C-/)
ee-redo ; Redo undone edit
; Display
ee-redisplay ; Redraw display (C-l)
ee-clear-screen ; Clear screen and redraw
; Information
ee-command-repeat ; Repeat last command
ee-set-mark ; Set mark at point (C-Space)
ee-exchange-point-and-mark ; Swap cursor and mark (C-x C-x)
ee-kill-region ; Kill between mark and point (C-w)
ee-copy-region ; Copy between mark and point (M-w)
; Help
ee-help ; Show help (if bound)
ee-describe-key ; Describe what key does
Complete command reference:
Category
Command
Description
Movement
ee-forward-char
Move right one character
ee-backward-char
Move left one character
ee-forward-word
Move forward one word
ee-backward-word
Move backward one word
ee-forward-exp
Move forward one S-expression
ee-backward-exp
Move backward one S-expression
ee-beginning-of-line
Move to line start
ee-end-of-line
Move to line end
Deletion
ee-delete-char
Delete char at cursor
ee-backward-delete-char
Delete char before cursor
ee-kill-line
Kill to end of line
ee-kill-word
Kill word forward
ee-backward-kill-word
Kill word backward
ee-kill-exp
Kill S-expression forward
History
ee-previous-history
Previous history entry
ee-next-history
Next history entry
ee-history-search-backward
Search history backward
ee-history-search-forward
Search history forward
Editing
ee-yank
Paste killed text
ee-transpose-char
Swap characters
ee-upcase-word
Uppercase word
ee-undo
Undo last change
Control
ee-accept
Accept and evaluate
ee-abort
Abort input
ee-complete
Tab completion
Creating New Editing Commands
Understanding the editor state:
; The expression editor maintains state including:
; - Current entry text (string or list of strings for multi-line)
; - Cursor position (line and column)
; - Mark position (for region operations)
; - Kill ring (list of killed text)
; - History (list of previous entries)
; - Undo history
; Commands are procedures that:
; 1. Receive editor state
; 2. Modify state as needed
; 3. Return updated state
; Basic structure of a custom command:
(define (my-command)
(lambda (ee entry)
; ee = editor state object
; entry = current text entry
; Return: (values new-entry update-needed?)
...))
; Editor state accessors (implementation-specific):
; These may vary by Chez Scheme version
Simple custom commands:
; Example: Insert current date
(define (ee-insert-date)
; This is a simplified conceptual example
; Actual implementation depends on editor internals
(let ([date-string (date->string (current-date) "~Y-~m-~d")])
(ee-insert-string date-string)))
; Bind to key
(ee-bind-key "\\C-xd" ee-insert-date)
; Example: Insert lambda character
(define ee-insert-lambda
(lambda ()
(ee-insert-string "λ")))
(ee-bind-key "\\C-xl" ee-insert-lambda)
; Example: Clear line and insert template
(define (ee-insert-define-template)
(ee-delete-entry)
(ee-insert-string "(define (name args)\n body)"))
(ee-bind-key "\\C-xD" ee-insert-define-template)
Commands using ee-string-macro:
; ee-string-macro: create command from key string
; Simulates typing a sequence of characters
; Insert template text
(define ee-insert-let-template
(ee-string-macro "(let ([])\\C-b\\C-b"))
(ee-bind-key "\\C-xL" ee-insert-let-template)
; Inserts "(let ([])" with cursor inside brackets
; Insert and position
(define ee-insert-if-template
(ee-string-macro "(if \\C-j \\C-j )\\C-p\\C-p\\C-e"))
(ee-bind-key "\\C-xI" ee-insert-if-template)
; Multi-line lambda template
(define ee-insert-lambda-template
(ee-string-macro "(lambda ()\\C-j )\\C-p\\C-e\\C-b"))
(ee-bind-key "\\C-x\\\\" ee-insert-lambda-template)
; Insert common patterns
(define ee-insert-define-syntax
(ee-string-macro "(define-syntax \\C-j (syntax-rules ()\\C-j [(_ )]))\\C-p\\C-p\\C-e"))
(ee-bind-key "\\C-xS" ee-insert-define-syntax)
Composing existing commands:
; ee-compose: combine multiple commands
; Executes commands in sequence
; Kill line and yank at different position
(define ee-duplicate-line
(ee-compose
ee-beginning-of-line
ee-set-mark
ee-end-of-line
ee-copy-region
ee-end-of-line
ee-newline
ee-yank))
(ee-bind-key "\\C-xc" ee-duplicate-line)
; Select entire entry
(define ee-select-all
(ee-compose
ee-beginning-of-entry
ee-set-mark
ee-end-of-entry))
(ee-bind-key "\\C-xa" ee-select-all)
; Kill entire entry
(define ee-kill-entire-entry
(ee-compose
ee-beginning-of-entry
ee-set-mark
ee-end-of-entry
ee-kill-region))
(ee-bind-key "\\C-xk" ee-kill-entire-entry)
; Uppercase entire word under cursor
(define ee-upcase-current-word
(ee-compose
ee-backward-word
ee-upcase-word))
(ee-bind-key "\\M-U" ee-upcase-current-word)
Commands with repeat count:
; Commands can respect repeat count (C-u prefix)
; C-u sets repeat count, default is 4
; C-u C-u is 16, C-u C-u C-u is 64, etc.
; C-u followed by digits sets explicit count
; Example: C-u 10 C-f moves forward 10 characters
; Most movement and deletion commands respect repeat count
; Custom command respecting repeat count
(define (ee-insert-multiple-chars char)
(lambda ()
; The editor provides repeat count to command
; Simplified example:
(let ([count (ee-get-repeat-count)])
(do ([i 0 (+ i 1)])
((= i count))
(ee-insert-string (string char))))))
; Insert multiple spaces
(define ee-insert-spaces
(ee-insert-multiple-chars #\space))
(ee-bind-key "\\M-SPC" ee-insert-spaces)
; M-SPC inserts 1 space, C-u M-SPC inserts 4 spaces
; Insert separator line
(define (ee-insert-separator)
(let ([count (or (ee-get-repeat-count) 40)])
(ee-insert-string (make-string count #\-))))
(ee-bind-key "\\C-x-" ee-insert-separator)
Context-sensitive commands:
; Commands that behave differently based on context
; Smart semicolon - add comment or toggle comment
(define (ee-smart-semicolon)
(let ([line (ee-current-line)])
(cond
; Already has comment - remove it
[(string-prefix? ";" (string-trim line))
(ee-beginning-of-line)
(ee-delete-char)]
; Add comment
[else
(ee-beginning-of-line)
(ee-insert-string "; ")])))
(ee-bind-key "\\C-x;" ee-smart-semicolon)
; Smart parenthesis - wrap selection or insert pair
(define (ee-smart-paren)
(if (ee-region-active?)
; Wrap region in parentheses
(begin
(ee-goto-mark)
(ee-insert-string "(")
(ee-exchange-point-and-mark)
(ee-insert-string ")"))
; Just insert pair and position
(begin
(ee-insert-string "()")
(ee-backward-char))))
(ee-bind-key "(" ee-smart-paren)
; Electric return - newline with smart indent
(define (ee-electric-return)
(ee-newline)
(ee-indent))
Commands with user input:
; Commands can prompt for input
; Go to specific line number
(define (ee-goto-line)
(let ([input (ee-prompt "Go to line: ")])
(when input
(let ([line-num (string->number input)])
(when (and line-num (positive? line-num))
(ee-goto-line-number line-num))))))
(ee-bind-key "\\M-g" ee-goto-line)
; Search and replace
(define (ee-search-replace)
(let ([search (ee-prompt "Search: ")])
(when search
(let ([replace (ee-prompt "Replace with: ")])
(when replace
(ee-replace-all search replace))))))
(ee-bind-key "\\M-%" ee-search-replace)
; Insert from history by pattern
(define (ee-insert-from-history)
(let ([pattern (ee-prompt "History pattern: ")])
(when pattern
(let ([match (find-history-match pattern)])
(when match
(ee-insert-string match))))))
(ee-bind-key "\\M-h" ee-insert-from-history)
; The expression editor handles multi-line input automatically
; When parentheses are unbalanced, Enter adds newline
; Example multi-line input:
; > (define (factorial n)
; (if (zero? n)
; 1
; (* n (factorial (- n 1)))))
; Navigation in multi-line mode:
; C-p / Up - Previous line (or previous history if at top)
; C-n / Down - Next line (or next history if at bottom)
; C-a - Beginning of current line
; C-e - End of current line
; M-< - Beginning of entire entry
; M-> - End of entire entry
; Multi-line history:
; Entire multi-line expressions stored as single history entry
; Navigate with C-p/C-n at entry boundaries
; Indentation in multi-line:
; Auto-indent based on paren depth
; Tab to manually indent
; ee-indent-all to reindent entire entry
Terminal compatibility:
; Expression editor adapts to terminal capabilities
; Check terminal type
(getenv "TERM") ; Should be set correctly
; Common terminal types:
; - xterm, xterm-256color
; - screen, screen-256color
; - vt100, vt220
; - linux (console)
; - dumb (no capabilities)
; If keys don't work, check terminal settings:
; 1. Verify TERM is set correctly
; 2. Check key sequences with: cat -v (then press keys)
; 3. Bind actual sequences your terminal sends
; Example: finding actual escape sequence
; $ cat -v
; (press Ctrl-Right)
; ^[[1;5C
;
; In Scheme: "\\e[1;5C"
; Some terminals need special configuration:
(ee-bind-key "\\e[1~" ee-beginning-of-line) ; Home (some terms)
(ee-bind-key "\\e[4~" ee-end-of-line) ; End (some terms)
(ee-bind-key "\\e[5~" ee-history-beginning) ; PgUp
(ee-bind-key "\\e[6~" ee-history-end) ; PgDn
; For dumb terminals, expression editor may be disabled
; Use rlwrap or similar as alternative
History file management:
; History persistence across sessions
(ee-history-file) ; Default location
; Custom history location
(ee-history-file
(string-append (getenv "HOME") "/.scheme_history"))
; Separate history per project
(define (set-project-history! project-name)
(ee-history-file
(format "~a/.scheme_history_~a"
(getenv "HOME")
project-name)))
; History is saved automatically:
; - On normal exit
; - Periodically during session (implementation-dependent)
; History format is typically plain text
; One entry per line (multi-line entries escaped)
; Clear history
(define (clear-history!)
(let ([file (ee-history-file)])
(when file
(when (file-exists? file)
(delete-file file)))))
; Export history
(define (export-history filename)
(let ([history (ee-history-list)])
(call-with-output-file filename
(lambda (port)
(for-each
(lambda (entry)
(fprintf port "~a~n~n" entry))
history))
'(replace))))
; Thread support requires threaded build of Chez Scheme
(threaded?) ; #t if threaded build
; Machine types with 't' prefix are threaded:
; ta6le - threaded, 64-bit Linux
; ta6osx - threaded, 64-bit macOS
; ta6nt - threaded, 64-bit Windows
; tarm64le - threaded, ARM64 Linux
(machine-type) ; Check current machine type
; If not threaded, thread procedures are not available
; Use threaded version: scheme (not petite)
Creating and starting threads:
; fork-thread creates and starts a new thread
(define t (fork-thread
(lambda ()
(display "Hello from thread!\n")
42)))
; Thread runs concurrently with main thread
; Returns thread object
; Wait for thread to complete
(define result (thread-join t))
; result = 42 (the return value)
; Thread with arguments (use closure)
(define (make-worker id)
(fork-thread
(lambda ()
(printf "Worker ~a starting~n" id)
(do-work id)
(printf "Worker ~a done~n" id))))
(define workers
(map make-worker '(1 2 3 4)))
; Wait for all workers
(for-each thread-join workers)
Thread object operations:
; Check if value is a thread
(thread? t) ; #t if t is a thread object
; Get current thread
(get-thread-id) ; Returns unique thread identifier
; Thread join waits for completion and returns value
(thread-join t) ; Block until t finishes
; Thread join with timeout (if supported)
; May not be directly available - use condition variables
; Check thread status (implementation-specific)
; Threads are either: running, finished, or errored
; Thread that signals error
(define t (fork-thread
(lambda ()
(error 'worker "something went wrong"))))
; thread-join re-raises the error
(guard (c [else (display-condition c)])
(thread-join t))
; Ensure code runs only once across threads
(define make-once
(lambda (thunk)
(let ([done #f]
[result #f]
[mutex (make-mutex)])
(lambda ()
(with-mutex mutex
(unless done
(set! result (thunk))
(set! done #t)))
result))))
; Usage
(define get-config
(make-once
(lambda ()
(printf "Loading config (only once)~n")
(load-config-file))))
; Multiple threads can call (get-config)
; Config loaded exactly once
Mutexes
Mutex basics:
; Mutexes provide mutual exclusion
(define m (make-mutex))
; Acquire mutex (blocks if held by another thread)
(mutex-acquire m)
; Release mutex
(mutex-release m)
; Check mutex name (optional, for debugging)
(define named-mutex (make-mutex 'my-mutex))
(mutex-name named-mutex) ; my-mutex
; Critical section pattern
(mutex-acquire m)
(guard (c [else (mutex-release m) (raise c)])
(critical-operation)
(mutex-release m))
with-mutex convenience form:
; with-mutex handles acquire/release automatically
(with-mutex m
(critical-operation))
; Equivalent to:
; (dynamic-wind
; (lambda () (mutex-acquire m))
; (lambda () (critical-operation))
; (lambda () (mutex-release m)))
; Safe even with exceptions
(with-mutex m
(error 'test "oops")) ; Mutex released despite error
; with-mutex returns body's value
(define result
(with-mutex m
(compute-something)))
; Nested with-mutex (different mutexes)
(with-mutex m1
(with-mutex m2
(operation-needing-both)))
Mutex operations:
; mutex-acquire with blocking control
(mutex-acquire m) ; Block until acquired
(mutex-acquire m #f) ; Non-blocking, returns #f if unavailable
; Try to acquire without blocking
(if (mutex-acquire m #f)
(begin
(critical-section)
(mutex-release m))
(handle-busy))
; Check if mutex is acquired
; No direct predicate - use try-acquire pattern
; Mutex fairness
; Chez Scheme mutexes are not guaranteed fair
; Long waits possible under contention
; Deadlock prevention
; Always acquire mutexes in consistent order
(define mutex-a (make-mutex 'a))
(define mutex-b (make-mutex 'b))
; Good: consistent order
(with-mutex mutex-a
(with-mutex mutex-b
(operation)))
; Bad: different order in different threads can deadlock
; Thread 1: lock a, lock b
; Thread 2: lock b, lock a
; Condition variables allow threads to wait for events
(define cv (make-condition))
; Condition with name (for debugging)
(define named-cv (make-condition 'my-condition))
(condition-name named-cv) ; my-condition
; Conditions are always used with a mutex
(define m (make-mutex))
; Wait on condition (must hold mutex)
(with-mutex m
(condition-wait cv m)) ; Releases mutex while waiting
; Signal one waiting thread
(condition-signal cv)
; Broadcast to all waiting threads
(condition-broadcast cv)
; condition-wait with timeout
(with-mutex m
(let ([result (condition-wait cv m
(make-time 'time-duration 0 5))]) ; 5 second timeout
(if result
(handle-signaled)
(handle-timeout))))
; Timeout returns #f, signal returns #t
; Implementing timeout pattern
(define (wait-with-timeout cv m seconds)
(let ([deadline (add-duration
(current-time 'time-utc)
(make-time 'time-duration 0 seconds))])
(condition-wait cv m deadline)))
; Timed wait loop
(define (wait-for-condition pred cv m timeout)
(let ([deadline (add-duration
(current-time 'time-utc)
(make-time 'time-duration 0 timeout))])
(with-mutex m
(let loop ()
(cond
[(pred) #t] ; Condition met
[(time>=? (current-time 'time-utc) deadline) #f] ; Timeout
[else
(condition-wait cv m deadline)
(loop)])))))
Spurious wakeups:
; condition-wait may return spuriously (without signal)
; Always recheck condition in a loop!
; WRONG - may proceed without condition being true
(with-mutex m
(condition-wait cv m)
(process-data)) ; Might not be ready!
; CORRECT - loop until condition is actually true
(with-mutex m
(let loop ()
(unless (data-ready?)
(condition-wait cv m)
(loop)))
(process-data)) ; Guaranteed ready
; Or using when
(with-mutex m
(while (not (data-ready?))
(condition-wait cv m))
(process-data))
; Helper macro
(define-syntax wait-until
(syntax-rules ()
[(_ cv m pred)
(let loop ()
(unless pred
(condition-wait cv m)
(loop)))]))
Condition variable visualization:
Locks
Recursive locks:
; Recursive locks (reentrant) can be acquired multiple times by same thread
; Standard mutexes are not recursive in Chez Scheme
; Implement recursive lock
(define (make-recursive-lock)
(let ([mutex (make-mutex)]
[owner #f]
[count 0]
[owner-mutex (make-mutex)]) ; Protects owner/count
(define (acquire)
(let ([self (get-thread-id)])
(with-mutex owner-mutex
(if (eq? owner self)
; Already own it, increment count
(set! count (+ count 1))
; Don't own it, need to acquire
(begin
(mutex-release owner-mutex) ; Release before blocking
(mutex-acquire mutex)
(mutex-acquire owner-mutex)
(set! owner self)
(set! count 1))))))
(define (release)
(with-mutex owner-mutex
(unless (eq? owner (get-thread-id))
(error 'recursive-lock "not owner"))
(set! count (- count 1))
(when (zero? count)
(set! owner #f)
(mutex-release mutex))))
(lambda (msg)
(case msg
[(acquire) (acquire)]
[(release) (release)]))))
; Usage
(define rlock (make-recursive-lock))
(rlock 'acquire) ; First acquisition
(rlock 'acquire) ; Second (same thread OK)
(rlock 'release) ; Count = 1
(rlock 'release) ; Count = 0, actually released
; Modern CPUs may reorder memory operations
; Threads may see writes in different orders
; Example of problematic code:
(define data #f)
(define ready #f)
; Thread 1 (producer)
(set! data (compute-value))
(set! ready #t)
; Thread 2 (consumer)
(when ready
(use data)) ; Might see ready=#t but old data!
; Problem: CPU/compiler might reorder writes
; Thread 2 might see ready=#t before data is updated
; Solution: Use proper synchronization
(define mutex (make-mutex))
(define cv (make-condition))
; Thread 1 (producer)
(with-mutex mutex
(set! data (compute-value))
(set! ready #t)
(condition-signal cv))
; Thread 2 (consumer)
(with-mutex mutex
(unless ready
(condition-wait cv mutex))
(use data)) ; Now guaranteed to see updated data
Memory barriers with mutex:
; Mutexes provide memory barriers (fences)
; Acquiring mutex: acquire barrier (reads can't move before)
; Releasing mutex: release barrier (writes can't move after)
; This ensures:
; 1. All writes before release are visible after acquire
; 2. Critical section operations don't leak out
; Visualization of memory barrier:
;
; Thread 1 Thread 2
; --------- ---------
; x = 1
; y = 2
; mutex-release -----> mutex-acquire
; read x (sees 1)
; read y (sees 2)
; Without mutex, reads might see old values
; Box operations provide some ordering
; box-cas! includes memory barriers
(define flag (box #f))
; Safe publication with CAS
(define published-data #f)
; Publisher
(set! published-data (create-data))
(box-cas! flag #f #t) ; Release barrier
; Consumer
(let spin ()
(unless (unbox flag) ; Acquire barrier with CAS below
(spin)))
; Now safe to read published-data
Safe publication patterns:
; Immutable data is always safe to share
; Once created, no synchronization needed
(define config (read-config-file)) ; Immutable after creation
; Any thread can read config safely
; For mutable data, use synchronization
; Pattern 1: Mutex-protected access
(define (make-safe-config)
(let ([data #f]
[mutex (make-mutex)])
(lambda (msg . args)
(with-mutex mutex
(case msg
[(get) data]
[(set!) (set! data (car args))])))))
; Pattern 2: Atomic box for single value
(define config-box (box #f))
(define (update-config! new-config)
(let loop ()
(let ([old (unbox config-box)])
(unless (box-cas! config-box old new-config)
(loop)))))
; Pattern 3: Copy-on-write
(define config-cow (box '()))
(define (add-config-entry! key value)
(let loop ()
(let* ([old (unbox config-cow)]
[new (cons (cons key value) old)])
(unless (box-cas! config-cow old new)
(loop)))))
; Readers never block - always see consistent snapshot
(define (get-config-entry key)
(let ([cfg (unbox config-cow)])
(cdr (assq key cfg))))
Memory consistency visualization:
Thread Parameters
Parameters are thread-local:
; Parameters have thread-local values
(define my-param (make-parameter 'default))
(my-param) ; 'default in main thread
; Each thread gets independent value
(fork-thread
(lambda ()
(my-param 'thread-value)
(my-param))) ; 'thread-value
(my-param) ; Still 'default in main thread
; parameterize is also thread-local
(fork-thread
(lambda ()
(parameterize ([my-param 'temporary])
(my-param)))) ; 'temporary
; After thread exits, no effect on main thread
Parameter inheritance:
; New threads inherit parameter values from creating thread
(define config (make-parameter 'initial))
(config 'main-value)
(define t (fork-thread
(lambda ()
(config)))) ; Inherits 'main-value
(thread-join t) ; Returns 'main-value
; Changes in child don't affect parent
(config 'main-value)
(define t2 (fork-thread
(lambda ()
(config 'child-value)
(config)))) ; 'child-value
(thread-join t2)
(config) ; Still 'main-value
; Use for configuration
(parameterize ([config 'special])
; All threads forked here inherit 'special
(fork-thread worker)
(fork-thread worker))
; Chez Scheme provides two hash table APIs:
; 1. R6RS hashtables (recommended): make-eq-hashtable, etc.
; 2. Legacy hash tables (compatibility): make-hash-table, etc.
; The legacy API predates R6RS and uses different naming
; It remains for backward compatibility with older code
; Key differences:
; - Different procedure names
; - Different iteration interface
; - Some different semantics
; Recommendation: Use R6RS hashtables for new code
; Use legacy hash tables only for maintaining old code
Legacy hash table comparison:
Operation
R6RS Hashtable
Legacy Hash Table
Create (eq)
(make-eq-hashtable)
(make-hash-table)
Create (eqv)
(make-eqv-hashtable)
(make-hash-table eqv?)
Create (equal)
(make-hashtable equal-hash equal?)
(make-hash-table equal?)
Lookup
(hashtable-ref ht key default)
(hash-table-ref ht key default)
Insert
(hashtable-set! ht key value)
(hash-table-set! ht key value)
Delete
(hashtable-delete! ht key)
(hash-table-delete! ht key)
Contains
(hashtable-contains? ht key)
(hash-table-contains? ht key)
Size
(hashtable-size ht)
(hash-table-size ht)
Iterate
(hashtable-for-each proc ht)
(hash-table-for-each ht proc)
Creating legacy hash tables:
; Basic creation - uses eq? by default
(define ht (make-hash-table))
; Specify comparison function
(define ht-eqv (make-hash-table eqv?))
(define ht-equal (make-hash-table equal?))
; With initial size hint
(define ht-sized (make-hash-table eq? 1000))
; Weak hash tables (keys held weakly)
(define wht (make-hash-table eq? #t)) ; Weak keys
; Check if hash table
(hash-table? ht) ; #t
(hash-table? '()) ; #f
; Note: Argument order differs from R6RS
; Legacy: (make-hash-table equiv? size weak?)
; R6RS: (make-eq-hashtable size)
; extend-syntax is a legacy macro system
; Predates syntax-rules and syntax-case
; Pattern-based but with different syntax
; Key characteristics:
; - Uses with clause for auxiliary patterns
; - Different pattern matching syntax
; - Explicit quasiquote in templates
; - No hygiene guarantees (like defmacro)
; Recommendation: Use syntax-rules or syntax-case for new code
; extend-syntax exists for compatibility with old code
; extend-syntax must be loaded from compatibility file
; (load "compat.ss") or enable in configuration
Basic extend-syntax form:
; Basic extend-syntax structure
(extend-syntax (macro-name)
[(pattern) template]
[(pattern) template]
...)
; Example: simple when macro
(extend-syntax (when)
[(when test body ...)
(if test (begin body ...))])
; Example: unless macro
(extend-syntax (unless)
[(unless test body ...)
(if (not test) (begin body ...))])
; Example: swap macro
(extend-syntax (swap!)
[(swap! a b)
(let ([temp a])
(set! a b)
(set! b temp))])
; Usage
(when (> x 0)
(display "positive")
(newline))
(let ([a 1] [b 2])
(swap! a b)
(list a b)) ; (2 1)
Pattern matching in extend-syntax:
; Patterns use similar notation to syntax-rules
; but with some differences
; Literal matching
(extend-syntax (my-cond else)
[(my-cond) #f]
[(my-cond (else e ...))
(begin e ...)]
[(my-cond (test e ...) clause ...)
(if test
(begin e ...)
(my-cond clause ...))])
; The second argument lists literal identifiers
; (my-cond else) - 'else' is a literal keyword
; Ellipsis matching
(extend-syntax (my-list)
[(my-list) '()]
[(my-list x) (cons x '())]
[(my-list x y ...) (cons x (my-list y ...))])
(my-list 1 2 3 4) ; (1 2 3 4)
; Nested patterns
(extend-syntax (my-let)
[(my-let ([var val] ...) body ...)
((lambda (var ...) body ...) val ...)])
(my-let ([x 1] [y 2])
(+ x y)) ; 3
; Original extend-syntax
(extend-syntax (old-when)
[(old-when test body ...)
(if test (begin body ...))])
; Equivalent syntax-rules
(define-syntax new-when
(syntax-rules ()
[(new-when test body ...)
(if test (begin body ...))]))
; extend-syntax with 'with' clause
(extend-syntax (old-or)
[(old-or) #f]
[(old-or e) e]
[(old-or e1 e2 ...)
(with ([temp (gensym)])
(let ([temp e1])
(if temp temp (old-or e2 ...))))])
; Equivalent syntax-rules (hygiene automatic)
(define-syntax new-or
(syntax-rules ()
[(new-or) #f]
[(new-or e) e]
[(new-or e1 e2 ...)
(let ([temp e1]) ; 'temp' is automatically fresh
(if temp temp (new-or e2 ...)))]))
; extend-syntax with literals
(extend-syntax (old-case else)
[(old-case val (else e ...))
(begin e ...)]
[(old-case val ((k ...) e ...) clause ...)
(if (memv val '(k ...))
(begin e ...)
(old-case val clause ...))])
; Equivalent syntax-rules
(define-syntax new-case
(syntax-rules (else) ; Literals declared here
[(new-case val (else e ...))
(begin e ...)]
[(new-case val ((k ...) e ...) clause ...)
(if (memv val '(k ...))
(begin e ...)
(new-case val clause ...))]))
When to use extend-syntax:
; Use extend-syntax only when:
; 1. Maintaining legacy code that uses it
; 2. Porting old code that depends on it
; 3. extend-syntax-specific features are needed
; For new code, prefer:
; - syntax-rules for simple pattern macros
; - syntax-case for complex macros
; Loading extend-syntax support
; May need to load compatibility file first:
; (load (string-append (scheme-directory) "/compat.ss"))
; Or import from compatibility library if available
; (import (compat))
; Check if extend-syntax is available
(guard (c [else #f])
(eval '(extend-syntax (test-macro)
[(test-macro) #t])
(interaction-environment))
#t) ; #t if available
Structures
Legacy structure overview:
; Chez Scheme has two record/structure systems:
; 1. R6RS records (recommended): define-record-type
; 2. Legacy structures (compatibility): define-structure
; Legacy structures are simpler but less powerful
; They predate the R6RS record system
; Key differences:
; - Simpler syntax
; - No inheritance
; - No opacity control
; - No custom constructors
; - No mutable/immutable field control
; Use define-record-type for new code
; Use define-structure for compatibility
Defining structures:
; Basic structure definition
(define-structure (point x y))
; This creates:
; - Constructor: (make-point x y)
; - Predicate: (point? obj)
; - Accessors: (point-x p), (point-y p)
; - Mutators: (set-point-x! p val), (set-point-y! p val)
; Usage
(define p (make-point 10 20))
(point? p) ; #t
(point-x p) ; 10
(point-y p) ; 20
(set-point-x! p 15)
(point-x p) ; 15
; Structure with more fields
(define-structure (person name age address))
(define john (make-person "John" 30 "123 Main St"))
(person-name john) ; "John"
(person-age john) ; 30
(set-person-age! john 31) ; Birthday!
; Original define-structure
(define-structure (point x y))
; Equivalent define-record-type
(define-record-type point
(fields
(mutable x)
(mutable y)))
; With custom names
(define-record-type point
(fields
(mutable x point-x set-point-x!)
(mutable y point-y set-point-y!))
(protocol
(lambda (new)
(lambda (x y)
(new x y)))))
; Structure with defaults
(define-structure (config host port timeout)
([host "localhost"]
[port 8080]
[timeout 30]))
; Equivalent record with protocol
(define-record-type config
(fields
(mutable host)
(mutable port)
(mutable timeout))
(protocol
(lambda (new)
(case-lambda
[() (new "localhost" 8080 30)]
[(host) (new host 8080 30)]
[(host port) (new host port 30)]
[(host port timeout) (new host port timeout)]))))
; Migration helper
(define-syntax define-compat-structure
(syntax-rules ()
[(_ (name field ...))
(define-record-type name
(fields (mutable field) ...))]))
Structure visualization:
Structure inspection:
; Structures can be inspected at runtime
(define-structure (point x y))
(define p (make-point 10 20))
; Structure type descriptor (implementation-specific)
; May not be directly accessible
; Printing structures
; Default print may show internals:
p ; #[point x: 10 y: 20] or similar
; Custom print can be set up if needed
; (record-writer (type-descriptor point)
; (lambda (r p wr)
; (fprintf p "#<point ~a,~a>" (point-x r) (point-y r))))
; Copying structures
(define (copy-point p)
(make-point (point-x p) (point-y p)))
; Comparing structures
(define (point=? p1 p2)
(and (= (point-x p1) (point-x p2))
(= (point-y p1) (point-y p2))))
; Convert to list
(define (point->list p)
(list (point-x p) (point-y p)))
; Create from list
(define (list->point lst)
(apply make-point lst))
Compatibility File
Loading compatibility features:
; Chez Scheme provides a compatibility file
; Contains legacy features not in the default environment
; Find the compatibility file
(define compat-file
(string-append (scheme-directory) "/compat.ss"))
; Load compatibility features
(load compat-file)
; Or specify full path if scheme-directory not set
(load "/usr/lib/csv9.5/compat.ss") ; Path varies
; After loading, legacy features are available:
; - extend-syntax
; - define-structure
; - Legacy hash tables
; - Other compatibility procedures
; Check what's loaded
(guard (c [else #f])
(extend-syntax (test) [(test) #t])
'extend-syntax-available)
Compatibility file contents:
; The compatibility file typically includes:
; 1. extend-syntax macro system
(extend-syntax (name literals ...)
[(pattern) template]
...)
; 2. define-structure for legacy records
(define-structure (name field ...)
([field default] ...))
; 3. Legacy hash table procedures
make-hash-table
hash-table-ref
hash-table-set!
hash-table-delete!
hash-table-for-each
; etc.
; 4. Legacy I/O procedures
; (if not already in base)
; 5. Legacy string procedures
; format variations, etc.
; 6. Other deprecated but useful forms
; The exact contents depend on Chez Scheme version
; Check the actual file for complete list
; ~/.schemerc - Automatic compatibility setup
; Detect if running in compatibility mode
(define *compat-mode*
(getenv "CHEZ_COMPAT_MODE"))
; Load compatibility features if needed
(when *compat-mode*
(let ([compat-file (string-append (scheme-directory) "/compat.ss")])
(when (file-exists? compat-file)
(load compat-file)
(printf "; Compatibility mode enabled~n"))))
; Or always load specific compatibility features
(define (ensure-compat-features)
; Check for extend-syntax
(unless (guard (c [else #f])
(eval 'extend-syntax (interaction-environment))
#t)
(let ([compat (string-append (scheme-directory) "/compat.ss")])
(when (file-exists? compat)
(load compat))))
; Add any shims needed
(unless (top-level-bound? 'hash-table-for-each)
; Define compatibility shim
(eval
'(define (hash-table-for-each ht proc)
(let-values ([(k v) (hashtable-entries ht)])
(vector-for-each proc k v)))
(interaction-environment))))
; Conditional compatibility based on code being loaded
(define (load-with-compat filename)
(parameterize ([source-directories
(cons (path-directory filename)
(source-directories))])
; Check file for compatibility markers
(when (file-uses-legacy-features? filename)
(ensure-compat-features))
(load filename)))
(define (file-uses-legacy-features? filename)
(call-with-input-file filename
(lambda (port)
(let loop ()
(let ([line (get-line port)])
(cond
[(eof-object? line) #f]
[(or (string-contains line "extend-syntax")
(string-contains line "define-structure")
(string-contains line "make-hash-table"))
#t]
[else (loop)]))))))
File Conventions in Chez Scheme
Scheme File Extensions
Common file extensions used across Scheme implementations:
Extension
Common Usage
Notes
.scm
Generic Scheme
Used by MIT Scheme, Guile, Chicken, Gambit, and others
.ss
Chez Scheme, PLT Scheme
Historical convention; PLT Scheme is now Racket
.sls
R6RS library source
Used by Chez, Ikarus, Vicare for libraries
.sps
R6RS program source
Top-level R6RS programs
.rkt
Racket-specific
Not compatible with other Schemes
.so
Compiled Chez object
Chez Scheme compiled output
File extensions do NOT determine implementation compatibility:
; A .scm file might contain:
; MIT Scheme specific code
(declare (usual-integrations)) ; MIT Scheme only
; Guile specific code
(use-modules (ice-9 match)) ; Guile only
; Chicken specific code
(require-extension srfi-1) ; Chicken only
; Chez Scheme specific code
(import (chezscheme)) ; Chez only
; The extension doesn't tell you which!
; The CODE INSIDE determines compatibility
Extensions accepted by Chez Scheme:
; Chez Scheme will load any of these:
(load "mycode.scm") ; Generic extension
(load "mycode.ss") ; Traditional Chez extension
(load "mycode.sls") ; R6RS library
(load "mycode.sps") ; R6RS program
(load "mycode.txt") ; Even arbitrary extensions!
; For compilation:
(compile-file "source.ss") ; Produces source.so
(compile-file "source.scm") ; Produces source.so
; Library file lookup considers extensions
(library-extensions) ; Shows configured extensions
; Default: ((".chezscheme.sls" . ".chezscheme.so")
; (".ss" . ".so")
; (".sls" . ".so")
; (".scm" . ".so"))
Identifying Chez Scheme Files
The (import (chezscheme)) convention:
; Best practice: use (import (chezscheme)) at the top
(import (chezscheme))
; This serves multiple purposes:
; 1. Fails immediately on other implementations
; 2. Self-documents the requirement
; 3. Provides full Chez functionality
(printf "Running on Chez Scheme ~a~n" (scheme-version))
(format #t "Platform: ~a~n" (machine-type))
Why this works as an identifier:
Alternative identification methods:
; Method 1: Shebang for executable scripts
#!/usr/bin/env scheme-script
(import (chezscheme))
(display "Hello from Chez!\n")
; Method 2: Editor mode line (Emacs style)
; -*- mode: scheme; scheme-implementation: chez -*-
(import (chezscheme))
...
; Method 3: Vim modeline
; vim: set ft=scheme:
(import (chezscheme))
...
; Method 4: Comment header
;;;; myprogram.ss
;;;; Requires: Chez Scheme 9.5+
;;;;
(import (chezscheme))
...
Comparison of identification approaches:
Method
Machine Readable
Self-Enforcing
Portable Syntax
(import (chezscheme))
Yes
Yes (fails on others)
Yes (R6RS import)
Shebang
Yes (Unix)
Yes (execution)
No (Unix only)
Editor mode line
By editors only
No
Yes (comment)
Comment header
No
No
Yes (comment)
File extension (.ss)
No
No
N/A
R6RS Library and Program Files
R6RS library file structure (.sls):
; File: my-utils.sls
(library (my-utils)
(export
string-join
string-repeat
println)
(import (chezscheme))
(define (string-join lst sep)
(if (null? lst)
""
(fold-left
(lambda (acc s) (string-append acc sep s))
(car lst)
(cdr lst))))
(define (string-repeat s n)
(apply string-append (make-list n s)))
(define (println . args)
(for-each display args)
(newline)))
; The (import (chezscheme)) marks this as Chez-specific
; Other implementations will fail to load it
; Using scheme-script (recommended)
#!/usr/bin/env scheme-script
; Using scheme with --script
#!/usr/bin/env -S scheme --script
; Using scheme with --program (R6RS program mode)
#!/usr/bin/env -S scheme --program
; Direct path (less portable)
#!/usr/local/bin/scheme-script
; Using petite (interpreter only, no compiler)
#!/usr/bin/env petite
(suppress-greeting #t)
Script vs program vs library:
Type
Extension
Shebang
Structure
Script
.ss, .scm
Optional
Top-level expressions
R6RS Program
.sps
Recommended
(import ...) body...
R6RS Library
.sls
No
(library ...) form
REPL Session
N/A
No
Interactive expressions
Compiled scripts for faster startup:
; Compile a script for faster execution
(compile-file "myscript.ss") ; Creates myscript.so
; Load compiled version (much faster startup)
(load "myscript.so")
; Compile a whole program
(compile-program "main.sps") ; Creates main.so
; Compile a library
(compile-library "mylib.sls") ; Creates mylib.so
; For deployment: compile everything
(compile-whole-program "main.wpo" "main.sps")
; Run compiled program directly
; $ scheme --program main.so
; In main.sps or a setup script
(library-directories
(cons "lib" (library-directories)))
; Or from command line
; $ scheme --libdirs lib:vendor main.sps
; In ~/.schemerc for global configuration
(library-directories
(append '("~/scheme/lib" "~/projects/common")
(library-directories)))
; Check current library directories
(printf "Library paths: ~s~n" (library-directories))