ClojureScript eval
If you look closely at the Clojure logo, you'll notice that it employs symbology—popularized by SICP—reflecting something that doesn't exist in ClojureScript.
Since bootstrapped ClojureScript provides a cljs.js/eval
capability—making this trivial—I thought I'd go ahead and land support for eval
in Planck that acts like clojure.core/eval
.
To use it, you simply need to refer it.
(require '[planck.core :refer [eval]])
With this, you can
(eval '(+ 1 2))
and get back 3
.
And, symbols are resolved honoring your current namespace, as in Clojure. If you (def a 3)
and (def b 4)
has been done in foo.core
then this
(eval '(+ a foo.core/b))
yields 7.
But, there is one interesting case I wanted to discuss. Perhaps this will be useful for you if you'd like to make use of cljs.js
to create an eval
capability.
Function Values
As expected, this yields 4
:
(eval '(inc 3))
But, what if you do this instead:
(eval (list inc 3))
If you don't do anything special in your eval
implementation, you will be rewarded with an error like this:
method in multimethod 'cljs.compiler/emit-constant' for dispatch value: function Function() {
[native code]
}
Hmm. The above works in Clojure. What's going on here?
It has to do with evaluation. In Clojure all of these work.
(eval (list '(var inc) 3))
(eval (list #'inc 3))
(eval (list inc 3))
If you start by thinking about how the Clojure evaluation model works with the expression
(#'inc 3)
you'll see that it essentially involves evaluating the item in function position in order to produce a function value. And, importantly, a function value evaluates to itself.
Arguably, without bootstrapped ClojureScript, the idea of evaluating a function value doesn't ever come up, as they are a runtime concept.
Planck's Approach
Planck is currently solving this by doing the following.
What you need, in this case, is something that can be emitted into the JavaScript which, when evaluated in JavaScript, evaluates to the JavaScript object that represents the function value. I know, I know … a lot of words. To put it another way, we have the object, but we need to emit something that, for lack of better words, succinctly points to it.
Importantly, this also needs to work for lambdas, so that something like this works:
(eval (list (fn [a b] (* a b)) 2 3))
So, Planck simply takes the function object, stashes it into a data structure, and emits JavaScript that fetches it out of this data structure, using a unique number as an index. It does this by extending the compiler for the cases of js/Function
and cljs.core/Var
, where emit-fn
does the stash/fetch logic just described.
(defmethod comp/emit-constant js/Function
[f]
(emit-fn f))
(defmethod comp/emit-constant cljs.core/Var
[f]
(emit-fn f))
So, this essentially turns functions into numbers. Maybe Gödel would be happy.
With this in place, a function value will evaluate to itself. For example, this yields true
(identical? inc
(eval (eval inc)))
and Planck's eval
handles all the corner cases above.
Perhaps something will ultimately make it into the ClojureScript compiler proper to handle cases like these. But, it is cool that defmulti
's open nature allows you to explore solutions outside of the compiler first to see what works in practice.