What is the purpose of ~' or '~ in Clojure

2020-05-19 05:34发布

问题:

I am learning about Clojure macros, and the code examples will sometimes have the constructs '~symbol or alternately ~'symbol. I know that (quote and ' prevent a form from being evaluated, and that the backquote additionally adds namespace qualification, and that ~ causes a quoted form to be evaluated. My question is: why is it useful to stop then start evaluation? I also assume that ~'symbol and '~symbol are different, but how so?

回答1:

~'symbol is used to produce an unqualified symbol. Clojure's macros capture namespace by default, so a symbol in a macro would normally be resolved to (your-namespace/symbol). The unquote-quote idiom directly results in the simple, unqualified symbol name - (symbol) - by evaluating to a quoted symbol. From The Joy Of Clojure:

(defmacro awhen [expr & body]
  `(let [~'it ~expr] ; refer to the expression as "it" inside the body
    (when ~'it
      (do ~@body))))

(awhen [:a :b :c] (second it)) ; :b

'~symbol is likely used to insert a name in the macro or something similar. Here, symbol will be bound to a value - let [symbol 'my-symbol]. This value is then inserted into the code the macro produces by evaluating symbol.

(defmacro def-symbol-print [sym]
  `(defn ~(symbol (str "print-" sym)) []
    (println '~sym))) ; print the symbol name passed to the macro

(def-symbol-print foo)
(print-foo) ; foo


回答2:

~ is a reader macro for the unquote function. within a quoted list it causes a symbol to be evaluated rather then used as a literal symbol

user> (def unquoted 4)
user>`(this is an ~unquoted list)
(user/this user/is user/an 4 clojure.core/list)
user> 

everything except the symbol unquoted was used just as a symbol where unquoted was resolved to its value of 4. this is most often used in writing macros. The repl also prints the namespace (user) infront of the names when it is printing the resulting list.

many macros, are basically just templates designed to do a bunch of slight variations on something that can't be done in a function. In this contrived example a template macro defines a function by producing a call to def. syntax-quote with unquoting makes this much easier to read:

user> (defmacro def-map-reducer [name mapper reducer] 
         `(defn ~name [& args#] 
              (reduce ~reducer (map ~mapper args#))))

#'user/def-map-reducer
user> (def-map-reducer add-incs inc +)
#'user/add-incs
user> (add-incs 1 2 3 4 5)
20

compared to:

user> (defmacro def-map-reducer [name mapper reducer] 
          (let [args-name (gensym)] 
              (list `defn name [`& args-name] 
                   (list `reduce reducer (list `map mapper args-name)))))

#'user/def-map-reducer
user> (def-map-reducer add-decs dec +)
#'user/add-decs
user> (add-decs 1 2 3 4 5)
10
user> 

in the second example I also don't use the auto-gensyms feature because I'm not in a syntax-quote