Search code examples
functional-programmingschemeexpressionlispracket

Why does this not evaluate in Scheme?


I am using the DrRacket environment to try out the Scheme language.

I defined sum+1 as follows:

(define sum+1 '(+ x y 1))

I was wondering why the following expression does not evaluate:

(let ([x 1] [y 2]) (eval sum+1))

whereas doing this returns the correct value:

(define x 1)
(define y 2)
(eval sum+1)

Solution

  • eval does not work with lexical variables at all unless the lexical variable was created in the same expression:

    #!r7rs 
    (import (scheme base)
            (scheme eval))
    
    (define env (environment '(scheme base)))
    
    (let ((x 10))
      (eval 'x env)) ; ERROR! `x` is not defined
    

    You can think of it as eval always happening top level with the global bindings of the environment you pass to the second argument. You can perhaps trick it by passing values from your lexical environment like this:

    (eval '(let ((x 10))
             x)
          env) ; ==> 10
    
    
    (let ((x 10))
      (eval `(let ((x ,x))
               x)
            env) ; ==> 10
    

    By the time most Scheme implementations run code local variables are usually stack allocated. Thus something imagine this code:

    (define (test v)
      (display v)
      (newline)
      (eval 'v))
    

    Might turn into this at runtime:

    (define (test 1 #f) ; indicates 1 argument, no rest
      (display (ref 0)) ; fetches first argument from stack
      (newline)
      (eval 'v))        ; but what is v?, certainly not the first argument
    

    Also you can make corner cases. What happens if you mutate?

    (define (test v)
      (eval '(set! v 10))
      v)
    

    The structure to eval might come from user input so it's not obvious that v gets mutated, also many compiling Scheme implementations need to treat variables that mutate differently so it needs to know before the code runs that v needs special treatment, but it is not decidable because the (set! v 10) might come from the database or user input. Thus by not including local bindings you save yourself a lot of trouble and the language gets easier to optimize and compile.

    There are lisp languages that can only be interpreted since it allows for passing macros as first class objects. These languages are impossible to reason about at compile time.