Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Contextual eval in clojure

Tags:

macros

clojure

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) 
like image 459
qed Avatar asked Sep 28 '22 20:09

qed


2 Answers

`'~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.

like image 129
Leon Grapenthin Avatar answered Oct 06 '22 00:10

Leon Grapenthin


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.

like image 36
amalloy Avatar answered Oct 06 '22 01:10

amalloy