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 Algol, Ada, C, Pascal and Scheme are statically 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). In Algol and Pascal (but not C or Ada) these scope rules also apply to the names of functions and procedures.
Static scoping is also sometimes called lexical scoping.
(define m 50) (define n 100) (define (hardy) (display (list "In Hardy, n=" n)) (newline)) (define (laurel n) (display (list "In Laurel, m=" m)) (newline) (display (list "In Laurel, n=" n)) (newline) (hardy)) n (laurel 1) (hardy)Output:
in main program -- n = 100 in laurel -- m = 50 in laurel -- n = 1 in hardy -- n = 100 (called from laurel) in hardy -- n = 100 (called from main)Here's a more complicated example:
(define foo 100) (define (operate-on op num) (op num foo)) (define (addup foo) (operate-on (lambda (x y) (+ foo x y)) 6)) (addup 10) => 116
We can declare a variable as dynamically scoped in Lisp using
defvar
. Example:
;; Repeat the examples above, but assume ;; that Scheme is dynamically scoped.
(define m 100) (define n 200) (define (dog n) (define (cat) (display (list "In cat, n=" n)) (newline)) (display (list "In dog m/n=" m n)) (newline) (cat)) (dog 1)Here's an example in Pascal:
begin integer m, n; procedure laurel(n: integer); begin procedure hardy; begin print("in hardy -- n = ", n); end; print("in laurel -- m = ", m); print("in laurel -- n = ", n); hardy; end; m := 50; n := 100; print("in main program -- n = ", n); laurel(1); /* we can't call hardy here, since the name isn't visible */ end;
Nesting procedures inside of other procedures interacts in interesting ways with recursion:
begin integer global, n; procedure laurel(n: integer); begin procedure hardy; begin print(global); print(n); end; if n<4 then laurel(n+1); else hardy; end; global := 99; n := 100; laurel(1); end;Here the output is:
99 4Scheme example:
(define global 99) (define n 100) (define (laurel n) (define (hardy) (display global) (newline) (display n) (newline)) (if (< n 4) (laurel (+ n 1)) (hardy))) (laurel 1)Note that when we finally call hardy, there are 5 different n's on the stack: the global one (with value 100), then 4 different invocations of laurel (with n=1, n=2, n=3, and n=4).
begin procedure test(n: integer, p: procedure); begin procedure rose; begin print("in procedure rose -- n="); print(n); end; print("in procedure test -- n="); print(n); p; if n<10 then begin if n=3 then test(n+1,rose) else test(n+1,p) end end; procedure violet; begin print("in procedure violet"); end; test(1,violet); end;Scheme example: (define (test n p) (define (rose) (display (list "In rose, n=" n)) (newline)) (display (list "In test, n=" n)) (newline) (p) (if (< n 5) (if (= n 3) (test (+ n 1) rose) (test (+ n 1) p)))) (define (violet) (display (list "In violet")) (newline)) (test 1 violet) Output:
(In test, n= 1) (In violet) (In test, n= 2) (In violet) (In test, n= 3) (In violet) (In test, n= 4) (In rose, n= 3) (In test, n= 5) (In rose, n= 3)
In Algol and Pascal, we can only pass procedures in as parameters -- we can't return a procedure or a function as a value from another function. We can do this in Scheme, however -- it means that Lisp can't always use a stack for storage allocation for local variables.
Blocks in Smalltalk also are lexically scoped, and include their environment of definition. Blocks can be returned from methods, assigned to global variables, and so forth -- so that storage for local variables can't always be allocated on a stack in Smalltalk either.
Example:
| a sum | a := Array new: 3. a at: 1 put: 10. a at: 2 put: 20. a at: 3 put: 30. sum := 0. a do: [:n | sum := sum+n].
More complicated examples: suppose we evaluate the following Smalltalk code.
(define x 100) (define (make-incrementer x) (lambda (q) (+ q x))) (defun test (p x) (p x)) (define add10 (make-incrementer 10)) (add10 x) (test add10 20) ;; smalltalk: | k | B1 := [k]. B2 := [:n | k := n+2]. k := 100.After we do this, the two global variables B1 and B2 are bound to blocks. The local variable k is no longer visible, but is still accessible and is shared by the two blocks. So
B1 value
will return 100. If we
evaluate B2 value: 5
, this will assign 7 to k. After that
evaluating B1 value
will return 7.