CSE 341 -- Lexical and Dynamic Scoping

Scope rules define the visibility rules for names in a programming language. What if you have references to a variable named k in different parts of the program? Do these refer to the same variable or to different ones?

Languages such as Haskell, Algol, Ada, C, Pascal and Scheme/Racket are lexically scoped. A block defines a new scope. Variables can be declared in that scope, and aren't visible from the outside. However, variables outside the scope -- in enclosing scopes -- are visible unless they are overridden (shadowed).

Lexical scoping is also called static scoping.

Lexical Scoping Examples

We'll use Scheme/Racket to write the examples -- but the concept applies in many different languages.

(define n 100)

(define (clam)
   (printf "in clam, n = ~a \n" n))

(define (squid n)
   (printf "in squid, n = ~a \n" n)
   (clam))

(printf "in main program, n = ~a \n" n)
(squid 1)
(clam)
Output:
in main program, n = 100
in squid, n = 1
in clam, n = 100     ;; called from squid
in clam, n = 100     ;; called from main

Dynamic Scoping

Some (mostly older) languages use dynamic scoping. A current example of a language with dynamic scoping is Emacs Lisp, the customization and extension language for emacs. Using the dynamic scoping rule, we first look for a local definition of a variable. If it isn't found, we look up the calling stack for a definition.

There are some situations in which dynamic scope is useful. For example, suppose we have a procedure for printing a financial summary that uses a variable currency_symbol. If we call the procedure from a context in which currency_symbol is bound to $ we show a dollar amount; if it is bound to € we get euros.

Dynamic Scoping Example

;; Repeat the  example above, but assume
;; that Scheme/Racket is dynamically scoped.
Output:
in main program, n = 100
in squid, n = 1
in clam, n = 1     ;; <== NOTE!!  called from squid
in clam, n = 100   ;; called from main

Problems with Dynamic Scope

Consider the following:

(define (mymap f s)
  (if (null? s)
      '()
      (cons (f (car s)) (mymap f (cdr s)))))

(let ((s 10))
  (mymap (lambda (r) (+ r s)) '(1 2 3)))

mymap is just like map in Scheme/Racket. With lexical scoping the result is (11 12 13). Just right.

But if we use dynamic scoping, we get an error: + complains that it can't add a number to a list!! When we evaluate the lambda, s is bound to the variable s in the definition of mymap.

The squid & clam example illustrates another aspect of this problem: the behavior of clam varies depending on where you call it from. You (and the compiler) can't just look at the code and figure out what the variables are bound to. This makes it both harder to reason about and harder to compile efficiently.

parameterize Form in Racket

The parameterize form provides a kind of dynamic scoping in Racket for specific variables. See Dynamic Binding: parameterize. This seems like a good approach: the clearer lexical scope rule is the default, but something like dynamic scoping is available if needed.

Exception Handlers

Dynamic scoping as the default behavior has disappeared from recent programming languages. The exception is exception handling! That essentially uses dynamic scoping to associate a handler with an exception.

Mini-Exercises

  1. What does this print? What would it print if Racket used dynamic scoping?
    (define y 0)
    (define (y-printer) (display y))
    
    (let ((y 10))
      (y-printer))
    
  2. What is the value of the let? What would it be if Racket used dynamic scoping?
    (define y 0)
    
    (let ((y 10)
          (f (lambda (x) (+ x y))))
      (f 1))
    
  3. Consider the following Racket code.
    (define n 0)
    
    (define (get-n) n)
    
    (define (squid) (get-n))
    
    (define (octopus n) (squid))
    
    what does (squid) evaluate to? What does (octopus 8) evaluate to? What would be the result in a dynamically scoped version of Racket?