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 `(a b c ~d e f g)
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.
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