Contextual eval in clojure - 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)

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

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.

Related

Is there a short form for creating hash-map in Clojure?

Is there a short form/macro that allows me to do
(defn f [a b c]
{a b c})
instead of
(defn f [a b c]
{:a a :b b :c c})
(defmacro as-map [& syms]
(zipmap (map keyword syms) syms))
Usage:
(def a 42)
(def b :foo)
(as-map a b)
;;-> {:a 42 :b :foo}
Note that to support namespaced keywords, you'd have to drop support for ns aliases if you want to keep it as short:
(defmacro as-map [& syms]
(zipmap (map keyword syms) (map (comp symbol name) syms)))
Usage:
(def a 42)
(def b :foo)
(as-map example/a foo-of/b)
;;-> {:example/a 42 :foo-of/b :foo}
Advice: Likely not a good idea, saves you a few keyboard hits at the cost of readability and expressivity and flexibility in naming local bindings.
This shows the steps. Remove the println's for actual use:
(ns clj.core
(:gen-class))
(defmacro hasher [& args]
(let [keywords (map keyword args)
values args
keyvals-list (interleave keywords values)
]
(println "keywords " keywords)
(println "values " values)
(println "keyvals-list " keyvals-list)
`(hash-map ~#keyvals-list)
)
)
(def a 1)
(def b 2)
(println \newline "result: " (hasher a b))
> lein run
keywords (:a :b)
values (a b)
keyvals-list (:a a :b b)
result: {:b 2, :a 1}
This is an old snippet of mine I've had kicking around for a while.
(declare ^:private restructure*)
(defn ^:private restructure-1 [m [e k]]
(cond
(= :strs e) (reduce #(assoc %1 (name %2) %2) m k)
(= :keys e) (reduce #(assoc %1 (keyword (namespace %2) (name %2)) %2) m k)
:else (assoc m k (restructure* e))))
(defn ^:private restructure* [form]
(if-not (map? form)
form
(as-> {} v
(reduce restructure-1 v form)
`(hash-map ~#(mapcat identity v)))))
(defmacro restructure [form]
(restructure* form))
The idea is that it provides the complement of clojure.core/destructure which goes from a destructuring form to bindings, this captures bindings and constructs a datastructure.
(let [x 1 y 2 z 3]
(restructure {:keys [x y z]}))
;; => {:x 1 :y 2 :z 3}

Nesting macros in Clojure

Consider this pseudo code:
(defrc name
"string"
[a :A]
[:div a])
Where defrc would be a macro, that would expand to the following
(let [a (rum/react (atom :A))]
(rum/defc name < rum/reactive []
[:div a]))
Where rum/defc is itself a macro. I came up with the code below:
(defmacro defrc
[name subj bindings & body]
(let [map-bindings# (apply array-map bindings)
keys# (keys map-bindings#)
vals# (vals map-bindings#)
atomised-vals# (atom-map vals#)]
`(let ~(vec (interleave keys# (map (fn [v] (list 'rum/react v)) (vals atomised-vals#))))
(rum/defc ~name < rum/reactive [] ~#body))))
Which almost works:
(macroexpand-all '(defrc aname
#_=> "string"
#_=> [a :A]
#_=> [:div a]))
(let* [a (rum/react #object[clojure.lang.Atom 0x727ed2e6 {:status :ready, :val nil}])] (rum/defc aname clojure.core/< rum/reactive [] [:div a]))
However when used it results in a syntax error:
ERROR: Syntax error at (clojure.core/< rum.core/reactive [] [:div a])
Is this because the inner macro is not being expanded?
Turns out the macro was working correctly but the problem occurred because < was inside the syntax quote it got expanded to clojure.core/<, and Rum simply looks for a quoted <, relevant snippet from Rum's source:
...(cond
(and (empty? res) (symbol? x))
(recur {:name x} next nil)
(fn-body? xs) (assoc res :bodies (list xs))
(every? fn-body? xs) (assoc res :bodies xs)
(string? x) (recur (assoc res :doc x) next nil)
(= '< x) (recur res next :mixins)
(= mode :mixins)
(recur (update-in res [:mixins] (fnil conj []) x) next :mixins)
:else
(throw (IllegalArgumentException. (str "Syntax error at " xs))))...

Strange error when using atoms inside deftype

I have the following code, defining a type that has an atom in there.
(defprotocol IDeck
(vec-* [dk] "Output to a persistent vector")
(count-* [dk] "Number of elements in the deck")
(conj1-* [dk & es] "Adding multiple elements to the deck"))
(deftype ADeck [#^clojure.lang.Atom val]
IDeck
(vec-* [dk] (->> (.val dk) deref (map deref) vec))
(count-* [dk] (-> (.val dk) deref count))
(conj1-* [dk & es]
(try
(loop [esi es]
(let [e (first esi)]
(cond
(nil? e) dk
:else
(do
(swap! (.val dk) #(conj % (atom e)))
(recur (rest esi))))))
(catch Throwable t (println t)))))
(defn new-*adeck
([] (ADeck. (atom [])))
([v] (ADeck. (atom (vec (map atom v))))))
(defn conj2-* [dk & es]
(try
(loop [esi es]
(let [e (first esi)]
(cond
(nil? e) dk
:else
(do
(swap! (.val dk) #(conj % (atom e)))
(recur (rest esi))))))
(catch Throwable t (println t))))
;; Usage
(def a (new-*adeck [1 2 3 4]))
(count-* a)
;=> 4
(vec-* a)
;=> [1 2 3 4]
(conj1-* a 1 2) ;; The deftype case
;=> IllegalArgumentException java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Long
(vec-* a)
;=> [1 2 3 4]
(conj2-* a 1 2) ;; The defn case
(vec-* a)
;=> [1 2 3 4 1 2]
Even though the two conj-* methods are exactly the same, except that one is in a deftype and the other is a normal defn, the first gives an error while the second succeeds. Why is this?
This is because protocols doesn't support variable number of arguments.
What you can do is make:
(conj1-* [dk & es] "Adding multiple elements to the deck"))
into
(conj1-* [dk es] "Adding multiple elements to the deck"))
such that the es param will be vector and called like:
(conj1-* a [1 2])

Clojure local-variables

I want to create a function (thunk) that will return successive elements in a list. What is the best way to do this? I wrote this code based on an apparently flawed understanding of how local variables in clojure work:
(defn reader-for [commands]
(with-local-vars
[stream commands]
(fn []
(let
[r (var-get stream)]
(if (empty? r)
nil
(let
[cur (first r)
_ (var-set stream (rest r))]
cur))))))
In this code I get:
#<CompilerException java.lang.IllegalStateException: Var null/null is unbound. (Chapel.clj:1)>
which seems to suggest that with-local-vars is dynamically scoped. Is that true? Is there any lexically scoped alternative? Thanks for any help.
If you require mutable state, use one of the clojure reference types:
user=> (defn reader-for [coll]
(let [a (atom coll)]
(fn []
(let [x (first #a)]
(swap! a next)
x))))
#'user/reader-for
user=> (def f (reader-for [1 2 3]))
#'user/f
user=> (f)
1
user=> (f)
2
user=> (f)
3
user=> (f)
nil
Also, let is for lexical scoping, binding is for dynamic scoping.
Edit: the thread-safe version as pointed out by Alan.
(defn reader-for [coll]
(let [r (ref coll)]
#(dosync
(let [x (first #r)]
(alter r next)
x))))
And just for fun, a thread-safe version with atoms (don't do this):
(defn reader-for [coll]
(let [a (atom coll)]
(fn []
(let [ret (atom nil)]
(swap! a (fn [[x & xs]]
(compare-and-set! ret nil x)
xs))
#ret))))

Does 'concat' break the laziness of 'line-seq'?

The following code appears to force line-seq to read 4 lines from file. Is this some kind of buffering mechanism? Do I need to use lazy-cat here? If so, how can I apply a macro to a sequence as if it were variadic arguments?
(defn char-seq [rdr]
(let [coll (line-seq rdr)]
(apply concat (map (fn [x] (println \,) x) coll))))
(def tmp (char-seq (clojure.contrib.io/reader file)))
;,
;,
;,
;,
#'user/tmp
Part of what you're seeing is due to apply, since it will need to realize as many args as needed by the function definition. E.g.:
user=> (defn foo [& args] nil)
#'user/foo
user=> (def bar (apply foo (iterate #(let [i (inc %)] (println i) i) 0)))
1
#'user/bar
user=> (defn foo [x & args] nil)
#'user/foo
user=> (def bar (apply foo (iterate #(let [i (inc %)] (println i) i) 0)))
1
2
#'user/bar
user=> (defn foo [x y & args] nil)
#'user/foo
user=> (def bar (apply foo (iterate #(let [i (inc %)] (println i) i) 0)))
1
2
3
#'user/bar