Self-referencing map literals

436 views
Skip to first unread message

Timo Mihaljov

unread,
Aug 31, 2009, 5:27:52 AM8/31/09
to Clojure
When defining a map literal, is it possible to reference the map that is
being defined?

I have some code that looks like this:

(let [radius 20
diameter (* 2 radius)
circumference (* pi diameter)]
{:radius radius
:diameter diameter
:circumference circumference})

I would like to simplify it to something like:

{:radius 20
:diameter (* 2 (% :radius))
:circumference (* pi (% :diameter))}

where % is the map itself.

Is this possible with the {}-syntax, or is there a macro to do this in
the standard library or contrib?

--
Timo

Meikel Brandmeyer

unread,
Aug 31, 2009, 6:12:16 AM8/31/09
to Clojure
Hi,

On Aug 31, 11:27 am, Timo Mihaljov <noid....@gmail.com> wrote:
> When defining a map literal, is it possible to reference the map that is
> being defined?

I don't think this is possible.

> I have some code that looks like this:
>
>     (let [radius 20
>           diameter (* 2 radius)
>           circumference (* pi diameter)]
>       {:radius radius
>        :diameter diameter
>        :circumference circumference})
>
> I would like to simplify it to something like:
>
>     {:radius 20
>      :diameter (* 2 (% :radius))
>      :circumference (* pi (% :diameter))}
>
> where % is the map itself.
>
> Is this possible with the {}-syntax, or is there a macro to do this in
> the standard library or contrib?

Simply define a helper:

(defn make-circle
[radius]
{:radius radius
:diameter (* 2 radius)
:circumference (* 2 pi radius)})

If you are concerned about the double calculation of the diameter you
can use a let as you did. Or can have a look here:
http://github.com/francoisdevlin/devlinsf-clojure-utils/ and here the
discussion: http://groups.google.com/group/clojure-dev/browse_thread/thread/4b20e40d83095c67.
Look for a function named trans*.

Hope this helps.

Sincerely
Meikel

Achim Passen

unread,
Aug 31, 2009, 6:56:15 AM8/31/09
to clo...@googlegroups.com
Hi!

Am 31.08.2009 um 11:27 schrieb Timo Mihaljov:

> I have some code that looks like this:
>
> (let [radius 20
> diameter (* 2 radius)
> circumference (* pi diameter)]
> {:radius radius
> :diameter diameter
> :circumference circumference})
>
> I would like to simplify it to something like:
>
> {:radius 20
> :diameter (* 2 (% :radius))
> :circumference (* pi (% :diameter))}
>
> where % is the map itself.

You could define your own let-like construct for this:


(defmacro letmap [[m kvs & mkvs] & body]
(if m
`(let [~m {}
~@(mapcat (fn [[k v]] `(~m (assoc ~m ~k ~v))) kvs)]
(letmap ~mkvs ~@body))
`(do ~@body)))


Usage:

(letmap [a-map {:a 12
:b (* 2 (:a a-map))}
another-map {:c (:a a-map)
:d (+ (:b a-map) (:c another-map))}]
[a-map, another-map])

-> [{:b 24, :a 12} {:d 36, :c 12}]


Hope this helps.

Kind Regards,
achim

Chas Emerick

unread,
Aug 31, 2009, 9:14:38 AM8/31/09
to clo...@googlegroups.com

On Aug 31, 2009, at 6:56 AM, Achim Passen wrote:

>> I would like to simplify it to something like:
>>
>> {:radius 20
>> :diameter (* 2 (% :radius))
>> :circumference (* pi (% :diameter))}
>>
>> where % is the map itself.
>
> You could define your own let-like construct for this:
>
>
> (defmacro letmap [[m kvs & mkvs] & body]
> (if m
> `(let [~m {}
> ~@(mapcat (fn [[k v]] `(~m (assoc ~m ~k ~v))) kvs)]
> (letmap ~mkvs ~@body))
> `(do ~@body)))
>
>
> Usage:
>
> (letmap [a-map {:a 12
> :b (* 2 (:a a-map))}
> another-map {:c (:a a-map)
> :d (+ (:b a-map) (:c another-map))}]
> [a-map, another-map])
>
> -> [{:b 24, :a 12} {:d 36, :c 12}]

Yeah, I needed something like this a while ago, and came up with this:

(defmacro let-map
"Equivalent of (let [a 5 b (+ a 5)] {:a a :b b})."
[kvs]
(let [keys (keys (apply hash-map kvs))
keyword-symbols (mapcat #(vector (keyword (str %)) %) keys)]
`(let [~@kvs]
(hash-map ~@keyword-symbols))))

user=> (let-map [a 5 b (inc a) c [a b] d {:foo c :bar (* a b)}])
{:a 5, :c [5 6], :b 6, :d {:foo [5 6], :bar 30}}

The nice thing about it is that you no longer need to use all the
keywords -- the definition of the map's slots becomes far more like
defining sequential bindings in a let.

- Chas

Timo Mihaljov

unread,
Aug 31, 2009, 10:54:11 AM8/31/09
to clo...@googlegroups.com

Thank you all for the answers!

I especially like Chas's solution. It didn't occur to me that the
redundancy can be eliminated by removing the map and leaving in the let
-- that's brilliant! :-) It's much easier to read because one doesn't
have to dig into the map to get to the variables, and it looks exactly
like the common idiom of interdependent let-bindings.

--
Timo

Reply all
Reply to author
Forward
0 new messages