Welcome! Please see the About page for a little more info on how this works.

0 votes
in Macros by

I have a function to capture code where i quote the code manually.
Usage looks like this

(statem/lazy-call `(page->csrf-token ~r))

But i always forget to backtick the sexp and to unquote all the symbols that are in the environment.

What would be the way to do this with a macro? I came up with this so far:

(deftype Call [code])

(defn call*
  [code]
  (Call. code))

(defmacro call
  [& body]
  `(call*
    ~(walk/postwalk
      (fn [d]
        (cond
          (instance? clojure.lang.IPersistentList d) `(list ~@d)
          :else  d))
      body)))

(let [foo (call (println "bar") '(foo bar) (+ 1 2))]
  (println (.code foo)))

but this does not work if quoted lists are in the body or other special forms.

1 Answer

+1 vote
by

My initial thought is that backtick would help a bit. Interesting question.

by
Thank you. I looked into it. There are things that i don't understand, eg.

        (symbol? form) `'~(resolve form)

What does the syntax-quote, quote, unquote part do?
by
It'll yield a quoted symbol, where the symbol has been resolved in the current namespace.

    user> `'~(resolve 'x)
    '#'user/x
by
I'm curious....in the original example you showed manual quasi-quoting and splicing....
In the final example, you just have normal forms with no quasi quoting.  Assumably, the expressions are combined and eval'd in some lexical environment.

If the intent is to capture code, why isn't

    (defmacro call
      [& body]
      `(call* '~body))

sufficient?  Is there some level of analysis (e.g. closing over vars) that I'm not seeing that you intend to include in the "call"?

If you want to capture the source and evaluate it, saving the source and the result in some structure, then it's still feasible with my proposal.   Something like  

    (deftype Call [code]
      clojure.lang.IFn
      (invoke [this] (eval `(do ~code))))  ;;probably would cache the result, this is a demo.

What am I missing?
by
Yes you are right. I simplified my example too much. I am actually closing over some let-bindings, method parameters and also vars.


    (let [n 1
          foo (call n '(foo bar) (+ n 2))]
      (.code foo))

The problem is that n is the symbol with your example but i want it to be the real value.
by
Hmm.  Seems as if you need information about the lexical environment to enable this sort of implicit quasiquoting.  That is why I mentioned using backtick; since `quasiquote` and the related splicing operations aren't exposed in clojure proper (the reader infers them on your behalf).  So you'd need to scrape the code and inject equivalent code.....or maybe there's a simple rule to just eval the vars in the form if it's not quoted.  Hmm
by
I finally have  this

    (deftype Call [code])

    (defn call*
      [code]
      (Call. code))

    (def ^:private gen-sym-var
      (let [f (.getDeclaredField clojure.lang.LispReader
                                 "GENSYM_ENV")]
        (.setAccessible f true)
        (.get f nil)))


    (def ^:private syntax-quote
      (let [m (.getDeclaredMethod clojure.lang.LispReader$SyntaxQuoteReader
                                  "syntaxQuote" (into-array Class [Object]))]
        (.setAccessible m true)
        (fn [form]
          (try
            (clojure.lang.Var/pushThreadBindings (hash-map gen-sym-var {}))
            (.invoke m nil (into-array Object [form]))
            (finally
              (clojure.lang.Var/popThreadBindings))))))

    (defmacro  call
      [& body]
      (let [ks (set (keys &env))
            myvar (walk/postwalk
                   (fn [sexp]
                     (if (contains? ks sexp)
                       (list 'clojure.core/unquote sexp)
                       sexp))
                   body)]
        `(call* ~(syntax-quote myvar))))

    (defn test
      [p]
      (let [n 1
            ^io.github.fp7.statem.Call foo (call n '(foo bar) (+ n 2) p)]
        (.code foo)))

    (test "")

It works but calls internal methods of clojure.  So i think i will stick to my manual quoting/unquoting for the time being. I also tried the same thing with backtick, but that threw some null pointers.

Thanks for your help!
...