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 locals

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

10.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"))