簡體   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