繁体   English   中英

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

[英]Clojure macros: quoting, unquoting and evaluation

我有以下代码:

(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)))

问题是,在评估最后一个表达式后,我得到了

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

我会在必要时解释代码,但是现在我只是陈述问题(这可能只是我正在制作的新手错误):

`(primitive? ~schema)

(primitive? schema)

在:else分支中分别返回true和false,因为我在cond表达式中使用第二个版本,它在不应该的地方失败(我更喜欢第二个版本,因为它将在编译时评估,如果我“没错。”

我怀疑它可能与名称空间限定的符号有关?

经过一些调查(见编辑),这是一个有效的Clojure替代品。 基本上,您很少需要递归宏。 如果你需要递归地构建表单,委托辅助函数并从宏调用它们(同样, 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))))))

宏采用缓冲区 (无论它是什么),由您的语言定义的模式以及为生成的函数访问的每个值调用的函数。

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

...产生以下内容:

(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)]))
  • 首先,评估缓冲区乐趣并将它们绑定到局部变量
  • 返回一个接受一个参数的闭包,并根据给定的模式对其进行解构,这要归功于Clojure的解构功能。
  • 对于每个值,使用适当的参数调用fun
  • 当架构为[::some x] ,接受零个或多个值作为向量,并为每个值调用函数fun 这需要通过循环来完成,因为只有在调用函数时才知道大小。

如果我们将向量[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

在这一行:

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

你在当前范围内调用write-fn ,其中s只是一个符号,而不是schema一个条目。 相反,您希望发出将在调用者范围内运行的代码:

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

并对if的另一个分支进行类似的更改。

顺便说一下,乍一看它看起来像这不是一个宏,但可能是一个更高阶的函数:接受一个模式或其他什么,并返回一个数据函数。 我的猜测是你把它作为表演的宏观,在这种情况下,我会建议你先用缓慢,简单的方法来试试。 一旦你有了工作,你可以在必要时使它成为一个宏。 或者,也许我错了,这里有一些基本上必须是宏观的东西。

暂无
暂无

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

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