我正在阅读优秀的书“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)
并将其转换为可以像
一样调用的宏(unit-of-time 4 h)
并在基本单位中给出结果(在这种情况下为秒)。我认为问题是“案例”中的Clojure / CL api更改。 CL中的“案例”如下所示:
(case 'a (('b) 'no) (('c) 'nope) (('a) 'yes))
但是在clojure ......
(case 'a 'b 'no 'c 'nope 'a 'yes)
如何方便。我在嵌套的defmacro中更改了我的anon函数,但它一直生成像
(case un#
s 1
(m 60)
(h 3600)
我该如何防止那些外围的?
答案 0 :(得分:6)
如果您将地图包裹在展平中,它应该会产生您想要的结果。
(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)))))))
发生了什么:在初始实现中,当你需要的是一个原子列表时,你的地图会返回一个列表列表。 Flatten采用任意深度的列表列表,并将其转换为单个值列表。
另一种方法是使用reduce而不是地图:
(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))))))
这样可以避免首先创建列表列表,因为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))))))
Mapcat实际上与reduce版本做同样的事情,但是隐式地为你处理concat。
答案 1 :(得分:4)
只需将map
更改为mapcat
。
(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))))))
详细说明并回复当前接受的答案:flatten
永远不会,永远不对(仅限专家使用的极少数例外情况)。最好是它是一个创可贴,可以为简单的输入提供正确的结果,但在复杂的输入上失败。相反,通常使用apply concat
将列表展平一个级别,或使用mapcat
而不是map
来生成已经更平坦的列表,或者更多涉及和专门的内容对于特别复杂的数据结构。在这里,只需要一个简单的mapcat
。
我想我应该注意defunits
宏的输入是如此严格约束(它们实际上必须是符号和数字),实际上没有输入flatten
会产生错误的输出。但是,扁平化是一种不好的习惯,无论如何都会产生更长,更复杂的代码。
答案 2 :(得分:3)
您可以连接括号对(使用,例如apply concat
)或使用amalloy的答案(最好的主意)。
您还可以使用seq函数构建返回表单:
(defmacro defunits [...]
(let [macro-name (symbol ...)
valsym (gensym "val__")
unitsym (gensym "unit__")]
(list `defmacro ; using ` so the symbol gets ns-qualified
macro-name
[valsym unitsym]
(make-body-expression ...)))) ; define appropriately
我认为这在这里是有意义的,因为无论如何你最终都没有引用任何东西,只有两级语法 - 引用;这主要是品味问题。