Local Bindings, Scope and Closures
10.1 let Forms
Common Lisp: let, let*, flet/labels (local functions). Clojure: let (sequential, with destructuring), letfn.
(let ((a 1) (b 2)) ; parallel binding
(+ a b)) ; 3
(let* ((a 1) (b (+ a 1))) ; sequential: b can see a
(* a b)) ; 2
(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1)))))
(odd? (lambda (n) (if (= n 0) #f (even? (- n 1))))))
(even? 10)) ; #t: mutually recursive locals10.2 Lexical Scope and Closures
(define (make-counter)
(let ((count 0))
(lambda ()
(set! count (+ count 1))
count)))
(define c1 (make-counter))
(define c2 (make-counter))
(c1) (c1) (c2) ; 1 2 1: each closure has its own state
(define (make-adder n) (lambda (x) (+ x n)))
((make-adder 10) 5) ; 15
(define (make-account balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount)) balance)
"Insufficient funds"))
(define (deposit amount) (set! balance (+ balance amount)) balance)
(lambda (msg . args) ; message passing (SICP)
(cond ((eq? msg 'withdraw) (apply withdraw args))
((eq? msg 'deposit) (apply deposit args))
(else (error "Unknown request" msg)))))
(define acc (make-account 100))
(acc 'withdraw 30) ; 70
(acc 'deposit 50) ; 12010.3 Lexical vs Dynamic Scope
Lexical scope (Scheme, Clojure, Common Lisp by default, Emacs Lisp with lexical-binding: t): a variable refers to the binding visible where the code was written.
Dynamic scope (Common Lisp special variables via defvar/defparameter, Emacs Lisp's historical default, Clojure's ^:dynamic vars with binding, Scheme/Racket parameters): a variable refers to the most recent binding at run time in the call stack. It's useful for configuration like output streams.
(define current-indent (make-parameter 0)) ; Racket / R7RS parameters
(define (show msg) (display (make-string (current-indent) #\space)) (display msg) (newline))
(parameterize ((current-indent 4)) (show "indented"))