Search code examples
clojureschemesicpevaluator

Evaluating "true" in meta-circular evaluator in Clojure


I converted the Structure and Interpretation of Computer Programs (SICP) version of the meta-circular evaluator to Clojure. The main difference (besides syntax) is the handling of the environment structure. Since you cannot use set-car! and set-cdr! in Clojure, these are implemented via an atom holding a map (copied from the code of Greg Sexton's chapter 4 notes on GitHub).

The code of the two evaluators can be found here:

The main procedure eval does a case analysis and then decides what to do next with the expression exp in the environment env:

(defn eval [exp env]
  (cond (self-evaluating? exp) exp
        (variable? exp) (lookup-variable-value exp env)
        (quoted? exp) (text-of-quotation exp)
        (assignment? exp) (eval-assignment exp env)
        (definition? exp) (eval-definition exp env)
        (if? exp) (eval-if exp env)
        (lambda? exp) (make-procedure (lambda-parameters exp) 
                                      (lambda-body exp)
                                      env)
        (begin? exp) (eval-sequence (begin-actions exp) env)
        (cond? exp) (eval (cond->if exp) env)
        (application? exp) (apply (eval (operator exp) env)
                                  (list-of-values (operands exp) env))
        :else (throw (Throwable. (str "Unknown expression type \"" exp "\" -- EVAL")))))

When interacting with the Clojure evaluator you can do things like:

;;; Eval input:
(defn hello-string hello)

;;; Eval value:
< environment map >

;;; Eval input:
hello-string

;;; Eval value:
hello

This shows new frames can be stored in and retrieved from the environment.

When the environment is setup initially, true and false are explicitly added:

(defn setup-environment []
  (let [initial-env
        (extend-environment primitive-procedure-names
                            primitive-procedure-objects
                            the-empty-environment)]
    (define-variable! 'true true initial-env)
    (define-variable! 'false false initial-env)
    initial-env))

But when an if-expression is entered, the code fails because it cannot find "true". (Same happens if you just evaluate true, which in the Scheme version evaluates to #t).

;;; Eval input:
(if true hello-string "hi")
CompilerException java.lang.Throwable: Unknown expression type "true" --
EVAL, compiling:(/home/erooijak/clojure/scheme-interpreter/scheme-
evaluator.clj:314:1)

(I would expect this to be evaluated to "hello")

Since eval-if works properly in the Scheme version (and does not work if true and false are not added to setup-environment, it looks like eval does not interpret true as something that needs to be looked up in the environment in the Clojure version.

Unfortunately, I do not see exactly how this looking-up happens in the Scheme version and why it does not happen in the Clojure version.

I hope someone can steer me in the right direction on why evaluating true works in the Scheme, but not in the Clojure implementation of the meta-circular evaluator.


Solution

  • I presume you are using Clojure's built-in reader, rather than implementing it yourself based on string inputs. true and false do not read as symbols, but rather as booleans, and then probably your variable? function doesn't return true for booleans.

    Relatedly, you write (define-variable! 'true true initial-env), as if you believe 'true and true are different values; they are the same, just as '6 and 6 are the same.