Ste*_*eve 2 macros clojure common-lisp
我正在阅读优秀的书Let Over Lambda,我试图将Common Lisp代码移植到Clojure上.
下面生成一个应该采用的宏
(defn defunits-chaining [u units prev]
(if (some #(= u %) prev)
(throw (Throwable. (str u " depends on " prev))))
(let [spec (first (filter #(= u (first %)) units))]
(if (nil? spec)
(throw (Throwable. (str "unknown unit " u)))
(let [chain (second spec)]
(if (list? chain)
(* (first chain)
(defunits-chaining
(second chain)
units
(cons u prev)))
chain)))))
(defmacro defunits [quantity base-unit & units]
`(defmacro ~(symbol (str "unit-of-" quantity))
[valu# un#]
`(* ~valu#
~(case un#
~base-unit 1
~@(map (fn [x]
`(~(first x) ;; <-- PRETTY SURE IT'S THIS `(
~(defunits-chaining
(first x)
(cons `(~base-unit 1)
(partition 2 units))
nil)))
(partition 2 units))))))
(defunits time s m 60 h 3600)
Run Code Online (Sandbox Code Playgroud)
并把它变成一个可以被称为的宏
(unit-of-time 4 h)
Run Code Online (Sandbox Code Playgroud)
并在基本单位中给出结果(在这种情况下为秒).我认为问题是"案例"中的Clojure/CL api更改.CL中的"Case"如下所示:
(case 'a (('b) 'no) (('c) 'nope) (('a) 'yes))
Run Code Online (Sandbox Code Playgroud)
但在clojure ...
(case 'a 'b 'no 'c 'nope 'a 'yes)
Run Code Online (Sandbox Code Playgroud)
如何方便.我在嵌套的defmacro中改变了我的anon函数,但是它一直在生成
(case un#
s 1
(m 60)
(h 3600)
Run Code Online (Sandbox Code Playgroud)
我怎样才能防止那些外围的?
如果您将地图包裹在展平中,它应该会产生您想要的结果.
(defmacro defunits [quantity base-unit & units]
`(defmacro ~(symbol (str "unit-of-" quantity))
[valu# un#]
`(* ~valu#
~(case un#
~base-unit 1
~@(flatten ;; <- changed this
(map (fn [x]
`(~(first x)
~(defunits-chaining
(first x)
(cons `(~base-unit 1)
(partition 2 units))
nil)))
(partition 2 units)))))))
Run Code Online (Sandbox Code Playgroud)
发生了什么:在初始实现中,当你需要的是一个原子列表时,你的地图会返回一个列表列表. Flatten采用任意深度的列表列表并将其转换为单个值列表.
另一种方法是使用reduce而不是map:
(defmacro defunits [quantity base-unit & units]
`(defmacro ~(symbol (str "my-unit-of-" quantity))
[valu# un#]
`(* ~valu#
~(case un#
~base-unit 1
~@(reduce (fn [x y] ;; <- reduce instead of map
(concat x ;; <- use concat to string the values together
`(~(first y)
~(defunits-chaining
(first y)
(cons `(~base-unit 1)
(partition 2 units))
nil))))
'()
(partition 2 units))))))
Run Code Online (Sandbox Code Playgroud)
这样可以避免首先创建列表列表,因为reduce会将所有传入的值汇总到一个结果中.
(defmacro defunits [quantity base-unit & units]
`(defmacro ~(symbol (str "unit-of-" quantity))
[valu# un#]
`(* ~valu#
~(case un#
~base-unit 1
~@(mapcat (fn [x] ;; <----- mapcat instead of map is the only change
`(~(first x)
~(defunits-chaining
(first x)
(cons `(~base-unit 1)
(partition 2 units))
nil)))
(partition 2 units))))))
Run Code Online (Sandbox Code Playgroud)
Mapcat实际上与reduce版本做同样的事情,但是隐式地为你处理concat.