简体   繁体   中英

Clojure macros symbol evaluation

I'm confused with how symbols are evaluated inside the macro. I tried the following example

(defmacro fx-bad
  [f x]
  `(f x))

(defmacro fx
  [f x]
  `(~f ~x))

(let [f inc x 1] (fx f x)) ;-> 2
(let [f inc x 1] (fx-bad f x)) ;-> exception

fx macro functions correctly, whereas fx-bad throws the exception

CompilerException java.lang.RuntimeException: No such var: user/f, compiling:(/tmp/form-init2718774128764638076.clj:12:18)

Are the symbols resolving inside the macro? Why fx-bad doesn't work but fx does?

--

Edit:

Apparently the exception has something to do with namespaces. Actually no arguments are ever evaluated in the macro. ~ in the syntax quote just produces the actual string (Symbol) passed to macro, without it the symbol inside the list is returned as it is.

Interesting thing is is, if the arguments supplied to macro call and symbols inside the quoted (not syntax quoted) list have equivalent names, they doesn't have to be unquoted, they are the same symbol anyway. This is good indication, how macro takes place before the evaluation and just manipulates raw symbols which doesn't mean anything at this point.

However, with the syntax quote case is different, and exception is thrown until symbols are unquoted, even thought the expanded macro looks like a valid line of code for evaluator to evaluate. Here are some examples

(defmacro fx
  [f x]
  `(~f ~x))

(defmacro fx-bad
  [f x]
  '(f x))

(defmacro fx-very-bad
  [f x]
  `(f x))


`(let [f inc x 1] ~(macroexpand '(fx f x)))
`(let [f inc x 1] ~(macroexpand '(fx-bad f x)))
`(let [f inc x 1] ~(macroexpand '(fx-very-bad f x)))

(macroexpand '(fx (fn [a] a) b))
(macroexpand '(fx-bad (fn [a] a) b))
(macroexpand '(fx-very-bad (fn [a] a) b))


(let [f inc x 1] (fx f x)) ;-> 2
(let [ff inc xx 1] (fx ff xx)) ;-> 2
(let [f inc x 1] (fx-bad f x)) ;-> 2
;(let [ff inc xx 1] (fx-bad ff xx)) ;-> exception
;(let [f inc x 1] (fx-very-bad f x)) ;-> exception

--

=> #'user/fx
#'user/fx-bad
#'user/fx-very-bad
(clojure.core/let [user/f clojure.core/inc user/x 1] (f x))
(clojure.core/let [user/f clojure.core/inc user/x 1] (f x))
(clojure.core/let [user/f clojure.core/inc user/x 1] (user/f user/x))
((fn [a] a) b)
(f x)
(user/f user/x)
2
2
2

So what is actually happening here, why the exception is thrown in the case of syntax quote?

Please see full details in Clojure for the Brave & True and most other Clojure books.

Basically, the backquote creates a template, and the tilde tells the compiler which values to substitute. In Groovy, bash, & other languages, it is like substitution of variables in a string:

f = "+";
x = 1;
y = 2;
result = "(${f} ${x} y)"

result => "(+ 1 y)"

In this example, the y is not substituted. The Clojure macro equivalent would be:

(let [f  '+
      x  1
      y  2]
  `(~f ~x y) )

;=> (+ 1 y)

Since the y was not "unquoted" by a tilde, it is taken literally and is not replaced with the contents of the variable y .

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM