Chapter 9: Error Handling
Learn to handle errors gracefully and debug your programs.
Basic Errors
The simplest way to signal an error:
(error "Something went wrong")
; Error: Something went wrong
(error "Division by zero" x y)
; Error: Division by zero (x y)This displays a message and returns #f. For more sophisticated handling, use the exceptions library.
Using guard
Import the exceptions module for structured error handling:
(import (sigil error))The guard form catches exceptions:
(guard (err
(else
(println "Caught an error: ~a" err)
#f))
(risky-operation))Handling Specific Errors
(define (safe-divide a b)
(guard (err
((string? err)
(println "Division error: ~a" err)
#f)
(else
(println "Unknown error")
#f))
(if (= b 0)
(raise "Cannot divide by zero")
(/ a b))))
(safe-divide 10 2) ; => 5
(safe-divide 10 0) ; => #f, prints "Division error: Cannot divide by zero"Raising Exceptions
(import (sigil error))
(define (require-positive n)
(unless (> n 0)
(raise (str "Expected positive, got " n)))
n)
(require-positive 5) ; => 5
(require-positive -1) ; raises exceptionCustom Error Types
For more complex applications, define custom error types with structs:
(import (sigil struct)
(sigil string))
;; Define a custom error type
(define-struct invalid-input
(value)
(message))
(define (validate-input input)
(when (string-blank? input)
(raise (invalid-input value: input
message: "Input cannot be blank"))))Defensive Programming
Checking Arguments
(define (process-list lst)
(unless (list? lst)
(error "Expected a list" lst))
(unless (not (null? lst))
(error "List cannot be empty"))
;; ... process ...
)Optional with Default
> (define (lookup key table default)
(let ((result (assoc key table)))
(if result
(cdr result)
default)))
> (lookup 'name '((age . 25)) "unknown")
"unknown"
> (lookup 'age '((age . 25)) 0)
25Null-Safe Operations
> (define (safe-car lst)
(if (pair? lst)
(car lst)
#f))
> (define (safe-cdr lst)
(if (pair? lst)
(cdr lst)
'()))
> (safe-car '(1 2 3))
1
> (safe-car '())
#f
> (safe-cdr '(1 2 3))
(2 3)
> (safe-cdr '())
()Debugging Techniques
Print Debugging
The simplest technique:
(define (problematic-function x)
(display "DEBUG: x = ")
(write x)
(newline)
;; ... rest of function
)A Debug Macro
(define-syntax debug
(lambda (form)
(let ((expr (cadr form)))
`(let ((result ,expr))
(display "DEBUG: ")
(display ',expr)
(display " = ")
(write result)
(newline)
result))))
(debug (+ 1 2))
;; Prints: DEBUG: (+ 1 2) = 3
;; Returns: 3Tracing Calls
(define (trace-calls proc name)
(lambda args
(display "CALL: ")
(display name)
(display " ")
(write args)
(newline)
(let ((result (apply proc args)))
(display "RETURN: ")
(display name)
(display " => ")
(write result)
(newline)
result)))
(define my-add (trace-calls + "add"))
(my-add 1 2 3)
;; CALL: add (1 2 3)
;; RETURN: add => 6Common Errors and Solutions
"Unbound variable"
Error: unbound variable: foo
The variable hasn't been defined. Check:
- Spelling
- Correct module imported
- Definition before use
"Expected pair"
Error: car: expected pair but got ()
You called car or cdr on an empty list. Add a null? check:
(if (null? lst)
'()
(car lst))"Arity mismatch"
Error: procedure expects 2 arguments, got 3
You passed the wrong number of arguments. Check the procedure signature.
"Type error"
Error: +: expected number but got "hello"
A procedure received the wrong type. Add type checks or validate inputs.
Pattern: Result Types
Return structured results instead of raising:
(import (sigil struct))
(define-struct result
(ok?)
(value)
(error))
(define (ok value)
(result ok?: #t value: value error: #f))
(define (err message)
(result ok?: #f value: #f error: message))
(define (safe-divide a b)
(if (= b 0)
(err "Division by zero")
(ok (/ a b))))
(let ((r (safe-divide 10 2)))
(if (result-ok? r)
(result-value r)
(begin
(println "~a" (result-error r))
0)))Best Practices
- Fail fast: Validate inputs early
- Be specific: Error messages should say what went wrong
- Provide context: Include relevant values in error messages
- Handle gracefully: Recover when possible, fail cleanly when not
- Test error cases: Write tests for error conditions
Practice Exercises
- Write a
safe-list-refthat returns a default value for out-of-bounds access. - Add error handling to the config file reader from Chapter 7.
- Create a validation library with helpful error messages.
What's Next
With the fundamentals covered, let's start building our text adventure game!