简体   繁体   English

Clojure宏:引用,不引用和评估

[英]Clojure macros: quoting, unquoting and evaluation

I have the following code: 我有以下代码:

(ns macroo)

(def primitives #{::byte ::short ::int})

(defn primitive? [type]
  (contains? primitives type))

(def pp clojure.pprint/pprint)

(defn foo [buffer data schema]
  (println schema))

(defmacro write-fn [buffer schema schemas]
  (let [data (gensym)]
    `(fn [~data]
       ~(cond
          (primitive? schema) `(foo ~buffer ~data ~schema)
          (vector? schema) (if (= ::some (first schema))
                             `(do (foo ~buffer (count ~data) ::short)
                                 (map #((write-fn ~buffer ~(second schema) ~schemas) %)
                                       ~data))
                             `(do ~@(for [[i s] (map-indexed vector schema)]
                                      ((write-fn buffer s schemas) `(get ~data ~i)))))
          :else [schema `(primitive? ~schema) (primitive? schema)])))) ; for debugging

(pp (clojure.walk/macroexpand-all '(write-fn 0 [::int ::int] 0)))

The problem is, upon evaluating the last expression, I get 问题是,在评估最后一个表达式后,我得到了

=>
(fn*
 ([G__6506]
  (do
   [:macroo/int :macroo/int true false]
   [:macroo/int :macroo/int true false])))

I'll explain the code if necessary, but for now i'll just state the problem (it might be just a newbie error I'm making): 我会在必要时解释代码,但是现在我只是陈述问题(这可能只是我正在制作的新手错误):

`(primitive? ~schema)

and

(primitive? schema)

in the :else branch return true and false respectively, and since i'm using the second version in the cond expression, it fails where it shouldn't (I'd prefer the second version as it would be evaluated at compile time if i'm not mistaken). 在:else分支中分别返回true和false,因为我在cond表达式中使用第二个版本,它在不应该的地方失败(我更喜欢第二个版本,因为它将在编译时评估,如果我“没错。”

I suspect it might have something to do with symbols being namespace qualified? 我怀疑它可能与名称空间限定的符号有关?

After some investigations (see edits), here is a working Clojure alternative. 经过一些调查(见编辑),这是一个有效的Clojure替代品。 Basically, you rarely need recursive macros. 基本上,您很少需要递归宏。 If you need to build forms recursively, delegate to auxiliary functions and call them from the macro (also, write-fn is not a good name). 如果你需要递归地构建表单,委托辅助函数并从宏调用它们(同样, write-fn不是一个好名字)。

(defmacro write-fn [buffer schemas fun]
  ;; we will evaluate "buffer" and "fun" only once
  ;; and we need gensym for intermediate variables.
  (let [fsym (gensym)
        bsym (gensym)]

    ;; define two mutually recursive function
    ;; to parse and build a map consisting of two keys
    ;;
    ;; - args is the argument list of the generated function
    ;; - body is a list of generated forms 
    ;;
    (letfn [(transformer [schema]
              (cond
                (primitive? schema)
                (let [g (gensym)]
                  {:args g
                   :body `(~fsym ~schema ~bsym ~g)})

                (sequential? schema)
                (if (and(= (count schema) 2)
                        (= (first schema) ::some)
                        (primitive? (second schema)))
                  (let [g (gensym)]
                    {:args ['& g]
                     :body
                     `(doseq [i# ~g]
                        (~fsym ~(second schema) ~bsym i#))})
                  (reduce reducer {:args [] :body []} schema))
                :else (throw (Exception. "Bad input"))))

            (reducer [{:keys [args body]} schema]
              (let [{arg :args code :body} (transformer schema)]
                {:args (conj args arg)
                 :body (conj body code)}))]

      (let [{:keys [args body]} (transformer schemas)]
        `(let [~fsym ~fun
               ~bsym ~buffer]
           (fn [~args] ~@body))))))

The macro takes a buffer (whatever it is), a schema as defined by your language and a function to be called for each value being visited by the generated function. 宏采用缓冲区 (无论它是什么),由您的语言定义的模式以及为生成的函数访问的每个值调用的函数。

Example

(pp (macroexpand
      '(write-fn 0 
                 [::int [::some ::short] [::int ::short ::int]] 
                 (fn [& more] (apply println more)))))

... produces the following: ...产生以下内容:

(let*
 [G__1178 (fn [& more] (apply println more)) G__1179 0]
 (clojure.core/fn
  [[G__1180 [& G__1181] [G__1182 G__1183 G__1184]]]
  (G__1178 :macroo/int G__1179 G__1180)
  (clojure.core/doseq
   [i__1110__auto__ G__1181]
   (G__1178 :macroo/short G__1179 i__1110__auto__))
  [(G__1178 :macroo/int G__1179 G__1182)
   (G__1178 :macroo/short G__1179 G__1183)
   (G__1178 :macroo/int G__1179 G__1184)]))
  • First, evaluate buffer and fun and bind them to local variables 首先,评估缓冲区乐趣并将它们绑定到局部变量
  • Return a closure which accept one argument and destructures it according to the given schema, thanks to Clojure's destructuring capabilities. 返回一个接受一个参数的闭包,并根据给定的模式对其进行解构,这要归功于Clojure的解构功能。
  • For each value, call fun with the appropriate arguments. 对于每个值,使用适当的参数调用fun
  • When the schema is [::some x] , accept zero or more values as a vector and call the function fun for each of those values. 当架构为[::some x] ,接受零个或多个值作为向量,并为每个值调用函数fun This needs to be done with a loop, since the size is only know when calling the function. 这需要通过循环来完成,因为只有在调用函数时才知道大小。

If we pass the vector [32 [1 3 4 5 6 7] [2 55 1]] to the function generated by the above macroexpansion, the following is printed: 如果我们将向量[32 [1 3 4 5 6 7] [2 55 1]]传递给上述宏展开生成的函数,则打印如下:

:macroo/int 0 32
:macroo/short 0 1
:macroo/short 0 3
:macroo/short 0 4
:macroo/short 0 5
:macroo/short 0 6
:macroo/short 0 7
:macroo/int 0 2
:macroo/short 0 55
:macroo/int 0 1

In this line: 在这一行:

`(do ~@(for [[i s] (map-indexed vector schema)]
         ((write-fn buffer s schemas) `(get ~data ~i)))))

you are calling write-fn , the macro , in your current scope, where s is just a symbol, not one of the entries in schema . 你在当前范围内调用write-fn ,其中s只是一个符号,而不是schema一个条目。 Instead, you want to emit code that will run in the caller's scope: 相反,您希望发出将在调用者范围内运行的代码:

`(do ~@(for [[i s] (map-indexed vector schema)]
         `((write-fn ~buffer ~s ~schemas) (get ~data ~i)))))

And make a similar change to the other branch of the if , as well. 并对if的另一个分支进行类似的更改。

As an aside, it looks to me at first glance like this doesn't really need to be a macro, but could be a higher-order function instead: take in a schema or whatever, and return a function of data. 顺便说一下,乍一看它看起来像这不是一个宏,但可能是一个更高阶的函数:接受一个模式或其他什么,并返回一个数据函数。 My guess is you're doing it as a macro for performance, in which case I would counsel you to try it out the slow, easy way first; 我的猜测是你把它作为表演的宏观,在这种情况下,我会建议你先用缓慢,简单的方法来试试。 once you have that working you can make it a macro if necessary. 一旦你有了工作,你可以在必要时使它成为一个宏。 Or, maybe I'm wrong and there's something in here that fundamentally has to be a macro. 或者,也许我错了,这里有一些基本上必须是宏观的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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