Search code examples
clojureschemesicpevaluatormetacircular

Attempting to call unbound fn, while I have defined it


I am trying to convert SICP's meta-circular evaluator to Clojure. In setup-environment a call to extend-environment does not compile because I get the error "Attempting to call unbound fn". Here's part of the code:

(... loads of methods for creating and managing environment list)

(def primitive-procedures
  (list (list 'car first)
        (list 'cdr rest)
        (list 'cons conj) ;; TODO: reverse
        (list 'null? nil?)
        (list 'list list)
        (list '+ +)
        (list '- -)
        (list '* *)
        (list '/ /)
        ;;      more primitives
        ))

(def primitive-procedure-names 
  #(map [first
         primitive-procedures]))

(def primitive-procedure-objects 
  (fn [] (map (fn [p] (list 'primitive (second p)))
               primitive-procedures)))

(def the-empty-environment '())

(defn extend-environment [vars vals base-env]
  (if (= (count vars) (count vals))
    (conj base-env (make-frame vars vals))
    (if (< (count vars) (count vals))
      (throw (Throwable. "Too many arguments supplied") vars vals)
      (throw (Throwable. "Too few arguments supplied") vars vals))))

;; Added # in front here so it could be called (???)
(defn setup-environment []
  #(let [initial-env
         (extend-environment (primitive-procedure-names)
                             (primitive-procedure-objects)
                             the-empty-environment)] ;; <= that does not work
     (define-variable! 'true true initial-env)
     (define-variable! 'false false initial-env)
     initial-env)))

;; Method for interacting with the evaluator:

(defn driver-loop []
  (prompt-for-input input-prompt)
  (let [input (read)]
    (let [output (m-eval input the-global-environment)]
      (announce-output output-prompt)
      (user-print output)))
  (driver-loop))

(...)

(def the-global-environment (setup-environment))
(driver-loop)

And when I evaluate the extend-environment method I get the following error:

  1. Caused by java.lang.IllegalStateException
    Attempting to call unbound fn:
    #'scheme-evaluator/extend-environment
    Var.java: 43 clojure.lang.Var$Unbound/throwArity
    AFn.java: 40 clojure.lang.AFn/invoke
    scheme-evaluator.clj: 277 scheme-evaluator/eval7808

I think I am not providing the right type of parameters or I have not created the right type of function. I tried various variations of anonymous methods and passing in parentheses or without, but I don't get it to compile.

Does anyone know what the reason is for this error and how can I fix it?


Solution

  • The definition of

    (def primitive-procedure-names 
      #(map [first
            primitive-procedures]))
    

    likely does not do what you intend. As written this defines a function that takes no arguments and returns transducer (which is a function) that will, if applied to a sequence substitute the values 0 and 1 for the functions first and primitive-procedures respectively. I'll demonstrate first with functions and then with values of numbers to make what's happening more clear (hopefully):

    user> (into [] (map [first 'example]) [0 1])
    [#function[clojure.core/first--4339] example]
    user> (into [] (map [1 2]) [0 1])
    [1 2]
    

    perhaps you wanted

    (def primitive-procedure-names 
     (map first primitive-procedures))
    

    And may I suggest using the defn form for defining functions and the def form for defining values unless you have a really strong reason not to.

    setup-environment is a function that returns a function which will if you call that function return a function that return's the initial-environment unmodified by the calls to define-variable. In Clojure the collection types are immutable so if you want to make several changes to a collection it's necessary to chain the result of adding the first one into the imput of adding the second one, then return the result of adding the second one:

    (add-second (add-first initial-value))
    

    which can also be written like this:

    (-> initial-value
        add-first
        add-second)
    

    which is just a shorthand for the example above.