I am working through the excellent book Let Over Lambda, and I am trying to port the Common Lisp code for defunits over to Clojure.
The following generates a macro that is supposed to take
(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)
and turn it into a macro that can be called like
(unit-of-time 4 h)
and give the results in the base unit (seconds in this case). I think is the problem is a Clojure/CL api change in "case". "Case" in CL looks like this:
(case 'a (('b) 'no) (('c) 'nope) (('a) 'yes))
but in clojure...
(case 'a 'b 'no 'c 'nope 'a 'yes)
HOW CONVENIENT. I changed my anon function in the nested defmacro, but it keeps generating like
(case un#
s 1
(m 60)
(h 3600)
How can I prevent those outer parens?
If you wrap your map in a flatten, it should produce the results your looking for.
(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)))))))
What's going on: In your initial implementation, your map is returning something a list of lists when what you need is a list of atoms. Flatten takes an arbitrarily deep list of lists and turns it into a single list of values.
Another approach would be to use a reduce rather than a 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))))))
This would avoid creating the list of lists in the first place as reduce rolls up all the passed in values into a single result.
Better yet, (Thanks to amalloy for spotting this one), there's 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))))))
Mapcat effectively does the same thing as the reduce version but implicitly handles the concat for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With