Contextual evaluation in clojure

Here is an example of the joy clojure of 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 is 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 versions above have a similar effect. Why did the author choose to use "?"

More detailed view:

(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 answer produces the following:

==>(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 do the job.

In fact, using `` v can cause some problems:

(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) 

      

+3


source to share


2 answers


`` ~ v is the way to return

(list 'quote v)

      



in this case, by specifying the actual value of v in the expression let

, not the symbol itself.

IDK The Joy Of Clojure, but apparently the authors want the forms passed to ctx not to be evaluated in the extended let form. E. g. (contextual-eval '{a (+ 3 4)} 'a)

will return (+ 3 4)

, but 7

in your versions that are the same in behavior.

+2


source


Your modified versions have the same effect only because you are trying to use them on very simple data. Instead, use type mapping {'a 'x}

, the context in which the binding for a

is a 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 evaluating the values ​​anchored to your characters twice: x

should not be evaluated since that value is actually a character x

. You avoid this double evaluation in your simple test cases because 1 evaluates itself: (eval (eval (eval 1))) will work just fine too. But this is not the case with most data structures, because they have non-trivial evaluation semantics.



Note also that the same expressions are the same in all cases, so you should never write any of them other than the first:

x
`~x
`~`~x
```~`~~`~~x

      

If you syntax-quote and then don't specify immediately, you have achieved nothing. So, if you ever find yourself writing a quote followed by an unreliable, it must be a big red flag that you are doing something wrong.

+1


source







All Articles