fn和let在clojure宏中



我遇到了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)]

在宏体(甚至其他宏)或递归函数或递归宏(调用其他宏的宏)中使用(helper)函数显示了lisp宏是多么强大。因为它表明在制定/定义宏时可以使用整个lisp。这是大多数语言的宏通常做不到的。

相关内容

  • 没有找到相关文章

最新更新