Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unquoting construction in contextual-eval in The Joy of Clojure

Tags:

clojure

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
like image 332
Daniel Gerigk Avatar asked Jan 27 '16 14:01

Daniel Gerigk


1 Answers

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" is special among the reader macros because you can only call that function via it's short form. You can't for instance call something like (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:

  • at macro-evaluation time: while the code is being formed. (side effects here require much forethought).
  • when your namespaces are loading: this is when forms at the top level run. This often happens when you start you program and before main is invoked.
  • things that run as a result of running main

ps: the above definitions are tailored to the context of this question and not intended to use the "official" terms.

like image 118
Arthur Ulfeldt Avatar answered Sep 28 '22 02:09

Arthur Ulfeldt