The following code is from chapter 8.1.1 of (the second edition of) The Joy of Clojure by Fogus, Houser:
(defn contextual-eval [ctx expr]
(eval
`(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)] ; Build let bindings at compile time
~expr)))
(contextual-eval '{a 1, b 2} '(+ a b))
;;=> 3
(contextual-eval '{a 1, b 2} '(let [b 1000] (+ a b)))
;;=> 1001
I do not really understand the meaning of the construction `'~v
. Can somebody please elaborate on that?
In the book, it is only said that
The bindings created use the interesting `'~v pattern to garner the value of the built bindings at runtime.
For example
(contextual-eval '{a 1, b 2} '(+ a b))
is expanded to
(let [a '1 b '2] (+ a b)))
and I don't understand why those quotes are introduced, what they are good for.
Also, we have the following behaviour:
(contextual-eval '{a 1, b (+ a 1)} '(+ a b))
ClassCastException clojure.lang.PersistentList cannot be cast to java.lang.Number
(defn contextual-eval' [ctx expr]
(eval
`(let [~@(mapcat (fn [[k v]] [k v]) ctx)]
~expr)))
(contextual-eval' '{a 1, b (+ a 1)} '(+ a b))
;=> 3
That expression uses almost all of the special line-noise-looking symbols available in Clojure, so it's worth picking it apart:
`
is a reader-macro for "syntax-quote" (syntax-quote something-here )
instead you would write `something-here
. It provides a rich set of options for specifying what parts of the expression after it should be evaluated and which should be taken literally. '
Is a reader-macro shortcut for the quote
special form. It causes the expression that it wraps not to be evaluated, and instead to be treated as data. If you wanted to write a literal quote
form without evaluating it, you could write `'something
to get `(quote something)
as the result. And this would cause the resulting quote expression not to be evaluated, just returned as is without running it yet.
~
is a part of the syntax of syntax-quote (it's "quote" with a syntax) that means "actually let this part run" so if you have a big list that you want taken literally (not run right now), except you have one item that you really do want evaluated right now, then you could write `(abc ~defg)
and d would be the only thing in that list that gets evaluated to whatever it's currently defined to be.
So now we can put it all together:
`'~
means "make a quote expression that contains the value of v as it is right now"
user> (def v 4)
#'user/v
user> `'~v
(quote 4)
And on to the motivation for this fancyness:
(contextual-eval '{a 1, b 2} '(+ a b))
seems like just adding some extra thinking without any benefit because it's basically just quoting the values 1 and 2. Since these are proper "values" they never change anyway.
Now if the expression was instead:
(contextual-eval
'{a (slurp "https://example.com/launch?getCode")
b the-big-red-button}
'(press b a))
Then it would make more sense to be careful about when that particular bit of code runs. So this pattern is about controlling which phase of a programs life actually runs the code. Clojure has several "times" when code can run:
main
is invoked. main
ps: the above definitions are tailored to the context of this question and not intended to use the "official" terms.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.