Here is an example from joy of clojure chapter 8:
(defn contextual-eval [ctx expr]
(let [new-expr
`(let [~@(mapcat (fn [[k v]]
[k `'~v])
ctx)]
~expr)]
(pprint new-expr)
(eval new-expr)))
(pprint (contextual-eval '{a 1 b 2} '(+ a b)))
I find the ``'` part quite perplexing, what's it for?
I also tried to modify the function a bit:
(defn contextual-eval [ctx expr]
(let [new-expr
`(let [~@(mapcat (fn [[k v]]
[k `~v])
ctx)]
~expr)]
(pprint new-expr)
(eval new-expr)))
(pprint (contextual-eval '{a 1 b 2} '(+ a b)))
(defn contextual-eval [ctx expr]
(let [new-expr
`(let [~@(vec (apply
concat
ctx))]
~expr)]
(pprint new-expr)
(eval new-expr)))
(pprint (contextual-eval '{a 1 b 2} '(+ a b)))
All the versions above have similar effect. Why did the author choose to use `' then?
A more detailed look:
(use 'clojure.pprint)
(defmacro epprint [expr]
`(do
(print "==>")
(pprint '~expr)
(pprint ~expr)))
(defmacro epprints [& exprs]
(list* 'do (map (fn [x] (list 'epprint x))
exprs)))
(defn contextual-eval [ctx expr]
(let [new-expr
`(let [~@(mapcat (fn [[k v]]
(epprints
(class v)
v
(class '~v)
'~v
(class `'~v)
`'~v
(class ctx)
ctx)
[k `~v])
ctx)]
~expr)]
(pprint new-expr)
(eval new-expr)))
(pprint (contextual-eval '{a (* 2 3) b (inc 11)} '(+ a b)))
This prints out the following in the repl:
==>(class v)
clojure.lang.PersistentList
==>v
(* 2 3)
==>(class '~v)
clojure.lang.PersistentList
==>'~v
~v
==>(class
(clojure.core/seq
(clojure.core/concat
(clojure.core/list 'quote)
(clojure.core/list v))))
clojure.lang.Cons
==>(clojure.core/seq
(clojure.core/concat (clojure.core/list 'quote) (clojure.core/list v)))
'(* 2 3)
==>(class ctx)
clojure.lang.PersistentArrayMap
==>ctx
{a (* 2 3), b (inc 11)}
==>(class v)
clojure.lang.PersistentList
==>v
(inc 11)
==>(class '~v)
clojure.lang.PersistentList
==>'~v
~v
==>(class
(clojure.core/seq
(clojure.core/concat
(clojure.core/list 'quote)
(clojure.core/list v))))
clojure.lang.Cons
==>(clojure.core/seq
(clojure.core/concat (clojure.core/list 'quote) (clojure.core/list v)))
'(inc 11)
==>(class ctx)
clojure.lang.PersistentArrayMap
==>ctx
{a (* 2 3), b (inc 11)}
==>new-expr
(clojure.core/let [a (* 2 3) b (inc 11)] (+ a b))
18
Again, using a single syntax quote for v seems to get the job done.
In fact, using `'v might cause you some trouble:
(defn contextual-eval [ctx expr]
(let [new-expr
`(let [~@(mapcat (fn [[k v]]
[k `'~v])
ctx)]
~expr)]
(pprint new-expr)
(eval new-expr)))
(pprint (contextual-eval '{a (inc 3) b (* 3 4)} '(+ a b)))
CompilerException java.lang.ClassCastException: clojure.lang.PersistentList cannot be cast to java.lang.Number, compiling:(/Users/kaiyin/personal_config_bin_files/workspace/typedclj/src/typedclj/macros.clj:14:22)
`'~v is a way to return
(list 'quote v)
in this case quoting the actual value of v in the let
expression, not the symbol itself.
IDK The Joy Of Clojure, but apparently the authors want to prevent forms passed in ctx from being evaluated in the expanded let form. E. g. (contextual-eval '{a (+ 3 4)} 'a)
will return (+ 3 4)
but 7
in your versions which are both identical in behavior.
Your modified versions have the same effect only because you're trying them on very simple data. Try instead with a mapping like {'a 'x}
, a context in which the binding for a
is the symbol x
.
user> (defn contextual-eval [ctx expr]
(let [new-expr
`(let [~@(mapcat (fn [[k v]]
[k `'~v])
ctx)]
~expr)]
(eval new-expr)))
#'user/contextual-eval
user> (contextual-eval {'a 'x} '(name a))
"x"
user> (defn contextual-eval [ctx expr]
(let [new-expr
`(let [~@(mapcat (fn [[k v]]
[k `~v])
ctx)]
~expr)]
(eval new-expr)))
#'user/contextual-eval
user> (contextual-eval {'a 'x} '(name a))
; Evaluation aborted.
The problem is that in your version, by neglecting the quote, you are double-evaluating the values bound to your symbols: x
shouldn't be evaluated, because the value is actually the symbol x
. You get away with this double evaluation in your simple test cases, because 1 evaluates to itself: (eval (eval (eval 1))) would work fine too. But doing that with most data structures is wrong, because they have non-trivial evaluation semantics.
Note also that the following expressions are identical in all cases, so there's never a reason to write any of them but the first one:
x
`~x
`~`~x
```~`~~`~~x
If you syntax-quote and then immediately un-quote, you haven't accomplished anything. So, if you ever find yourself writing a quote followed by an unquote, this should be a big red flag that you are doing something wrong.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With