Search code examples
scopeschemelisplexical

Scope in Scheme (Lisp)


I know that scheme is a lexically scoped/statically scoped language, but I don't understand why these two blocks of code return different results.

   (define a 100)
   (let ((z 20))
      (lambda (a b) (+ a b)) a z)

20

 (define a 100)
 (let ((z 20)) 
      (lambda (a b) (+ a b)) z a)

100


Solution

  • Both your code blocks have similar syntax. A lexical variable z with two lines of dead code and a expression in tail position that becomes the result of the let. eg.

    (let ((z 20))
      (lambda (a b) (+ a b)) ; evaluates to a procedure and thrown away
      a                      ; evaluated to 100 and thrown away 
      z)                     ; evaluated to 20. in tail so the result of let
    

    The second is very similar:

     (let ((z 20)) 
       (lambda (a b) (+ a b)) ; evaluates to a procedure and thrown away
       z                      ; evaluated to 20 and thrown away
       a)                     ; evaluated to 100. In tail so the result of let
    

    Notice that the lambda that evaluates to procedures are never applied (called). To call them eg. with the other expressions as arguments you get completely different result:

    (let ((z 20))
      ; calls the anonymous lambda with agruments a and z
      ((lambda (a b) (+ a b)) a z)) ; ==> 120
    

    Parentheses matters in this language. Too few and you get separate expressions where the last is the result. Too many and you are accidentally applying values that are not procedures.

    Also note that in a dynamically scoped lisp you would have got the same result. In this however:

    (define n 100)
    (define (make-incer n)
      (lambda (x) 
        (+ n x)))
    
    (define inc10 (make-incer 10))
    (inc10 5) 
    ; ==> 15 (lexically scoped) 
    ; ==> 105 (dynamicly scoped)
    

    The reason is that dymamicly coped languages don't have closures and thus n doesn't exist when make-incer has returned the procedure and thus n is the global binding or closer call time binding like here:

    (let ((n 5))
      (inc10 20)) 
    ; ==> 30 (lexically scoped)
    ; ==> 25 (dynamically scoped)