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。大多数语言的宏通常无法做到的事情。
我 运行 了解 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。大多数语言的宏通常无法做到的事情。