【问题标题】:Clojure macros: quoting, unquoting and evaluationClojure 宏:引用、取消引用和求值
【发布时间】:2015-09-09 13:56:17
【问题描述】:

我有以下代码:

(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 macros quoting


    【解决方案1】:

    经过一些调查(请参阅编辑),这是一个可用的 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)]))
    
    • 首先,评估 buffer 和 fun 并将它们绑定到局部变量
    • 由于 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
    

    【讨论】:

    • 该函数的目的是:对于给定的“模式”,创建一个函数,该函数采用数据结构(由模式描述)并将其转换为字节。目标是在编译时创建此函数,并通过内联函数参数使其尽可能密集。所以 - 最佳 - 宏扩展函数不会包含像 ((fn [x] x) y) 这样的列表。
    • @ViktorM。我编辑了答案(我最终会清理所有内容,但现在我想看看实际问题是什么)
    • 虽然我不能真正理解 CL 代码(从不摆弄它),但据我所知,它看起来很有希望。那种破坏性的东西是我没有探索过的。关于 ::some 案例:[::some ::int] 基本上意味着数据结构包含一些整数,其计数在编译时是未知的(这是我为自己定义的一些自定义 DSL)。
    • 关于您上次的编辑:您的代码看起来很漂亮,虽然需要一些时间来消化!如果我有其他问题,我会尽快回复您(希望您不介意)。
    • 有个小问题:'::some' 后面可以跟任何有效的模式,而不仅仅是原语。我担心这可能是单独解构无法处理的事情。我会调查可以做些什么,如果我不能解决,我会发布一个后续问题。
    【解决方案2】:

    在这一行:

    `(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 的另一个分支也进行类似的更改。

    顺便说一句,在我看来,这实际上并不需要是一个宏,而是可以是一个高阶函数:接受一个模式或其他什么,并返回一个数据函数。我的猜测是您将其作为性能宏来执行,在这种情况下,我建议您先尝试一下缓慢而简单的方法;一旦你有这个工作,你可以在必要时把它变成一个宏。或者,也许我错了,这里有一些东西基本上必须是一个宏。

    【讨论】:

    • 我已经尝试过您的建议 - 它工作正常,但宏扩展形式充满了 ((fn [x] x) y) 之类的表达式,我担心这会影响性能。我试图做的是将上述列表减少到 y-s (我已经查找了 definline 和 :inline 元数据,但我不知道这些是否与我的问题有关)。至于它是否是宏:我最近才涉足宏的世界,所以可能一个函数就足够了。
    • 好吧,你不能用这种方式真正解决“愚蠢”宏扩展的问题:通过省略语法引用,你完全改变了表达式的含义,它基本上变成了无稽之谈。如果您的函数的工作版本正确,请使用它:如果在计时后发现它太慢,请再问一个关于如何解决您愚蠢的宏扩展的问题。
    • 当你说“傻”时:你的意思是我的失败,还是我不得不忍受 clojure 的问题?
    • 我的意思是你的宏已经扩展到你个人认为很愚蠢的代码:((fn [x] x) y) 而不是y。无论这是你的错还是clojure的(不是clojure的)都与我的观点无关,也就是说,在你有证据证明这对你来说是一个实际的问题而不是愚蠢之前,你不妨使用你的代码已经写了已经有效的。如果您认为这还不够,无论出于何种原因,您都应该寻求帮助以改进已经正确生成的代码,而不是提供一些损坏的代码并说“我该如何使它正确”。
    • 关于你的最后半句话:我相信我已经明确表示我对这个主题并不精通,我已经准备好指定与我的代码和动机相关的任何内容(是的,我事先做了我的研究)。忘记这一点,我感谢您的反馈,并感谢您让我走上正轨。祝你有美好的一天!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多