fn 和 let inside clojure 宏

fn and let inside clojure macro

我 运行 了解 Clojure 宏的一些限制。请问如何优化下面的代码?

(defmacro ssplit-7-inefficient [x]
  (let [t 7]
    ;;                    Duplicated computation here!
    `(do [(first          (split-with #(not (= '~t %)) '~x))
          (drop 1 (second (split-with #(not (= '~t %)) '~x)))])))

(ssplit-7-inefficient (foo 7 bar baz))
;; Returns: [(foo) (bar baz)]

以下是一些行不通的方法:

(defmacro ssplit-7-fails [x]
  (let [t 7]
    `(do ((fn [[a b]] [a (drop 1 b)]) (split-with #(not (= '~t %)) '~x)))))

(ssplit-7-fails (foo 7 bar baz))
;; Error: Call to clojure.core/fn did not conform to spec.

(defmacro ssplit-7-fails-again [x]
  (let [t 7]
    `(do
       (let [data (split-with #(not (= '~t %)) '~x)]
         ((fn [[a b]] [a (drop 1 b)]) data)))))

(ssplit-7-fails-again (foo 7 bar baz))
;; Error: Call to clojure.core/let did not conform to spec.

请注意 split-with 只拆分一次。您可以使用一些解构来获得您想要的东西:

(defmacro split-by-7 [arg]
  `((fn [[x# [_# & z#]]] [x# z#]) (split-with (complement #{7}) '~arg)))

(split-by-7 (foo 7 bar baz))
=> [(foo) (bar baz)]

在其他用例中,partition-by 也很有用:

(defmacro split-by-7 [arg]
  `(->> (partition-by #{7} '~arg)
        (remove #{[7]})))

(split-by-7 (foo 7 bar baz))
=> ((foo) (bar baz))

在 Clojure 中推理宏并不是那么容易 - (在我看来 macroexpand-1 使代码疏远了很多 - 与 Common Lisp 的 macroexpand-1 相比......)。

我的方法是首先构建一个辅助函数。

(defn %split-7 [x]
  (let [y 7]
    (let [[a b] (split-with #(not= y %) x)]
      [a (drop 1 b)])))

此函数使用解构,因此 split-with 是“高效的”。

它几乎完全按照宏应该做的去做。只有那个必须引用 参数 - 以便它起作用。

(%split-7 '(a 7 b c)) 
;;=> [(a) (b c)]

从这一步到宏并不难。

宏应该在插入辅助函数的调用时自动引用参数。

(defmacro split-7 [x]
  `(%split-7 '~x))

这样我们就可以调用:

(split-7 (a 7 b c))
;; => [(a) (b c)]

使用这个技巧,甚至可以将函数概括为:

(defn %split-by [x y]able like this
  (let [[a b] (split-with #(not= y %) x)]
    [a (drop 1 b)]))

(defmacro split-by [x y]
  `(%split-by '~x ~y))

(split-by (a 7 b c) 7)
;; => [(a) (b c)]

(split-by (a 7 b c 9 d e) 9)
;; => [(a 7 b c) (d e)]

在宏体中使用(辅助)函数 - 甚至其他宏 - 或递归函数或递归宏 - 调用其他宏的宏 - 显示了 lisp 宏的强大之处。因为它表明您可以在 formulating/defining 宏时使用整个 lisp。大多数语言的宏通常无法做到的事情。