Patch: java.util.Map support

8 views
Skip to first unread message

Achim Passen

unread,
Sep 29, 2008, 2:30:40 PM9/29/08
to Clojure
Hi!

Here's a patch to add java.util.Map support to clojure maps, along
with some example code. This my first java code in quite some time, so
if you notice i'm doing anything odd or incorrect, please don't
hesitate to comment.

http://groups.google.com/group/clojure/web/jmap.diff
http://groups.google.com/group/clojure/web/jmap-examples.clj

It has been discussed why clojure maps do not implement the
java.util.Map interface (short version: j.u.Map and j.u.Collection
clash, Collection being preferrable). This patch provides two
functions, "jmap" and "cmap", the former to wrap a clojure map in a
flyweight wrapper that provides the j.u.Map implementations, the
latter to unwrap it again.

The results of a calls to j.u.Map methods are automatically wrapped
again if they return maps, so from the java point of view, nested
clojure maps show up as nested java maps.

From the clojure point of view, wrapped maps retain most of the
functionality of their unwrapped counterparts: they can be invoked,
assoced and conjed, etc., the result being a full-featured clojure map
in each case. However ...

Note: Since clojure maps are no java maps, they can't be equal to java
maps. This holds for wrapped clojure maps as well - surprisingly (in a
bad sense), but i don't see a way around this:

(= (doto (HashMap.) (put 1 2)) {1 2})
-> false

(= (jmap {1 2}) {1 2})
-> false

Efficiency: lookup key- and mapentry-sets on wrapped maps should be
fast, since they just delegate calls to the clojure map inside. Lookup
on the values takes linear time though, and as far as i can see, we
can't do better, because the values themselves are not structured for
fast access in clojure maps.

Comments are most welcome!

Kind regards,
achim

--
http://rauschabstand.twoday.net

Stephen C. Gilardi

unread,
Sep 29, 2008, 3:20:31 PM9/29/08
to clo...@googlegroups.com
Hi achim,

That looks like a fine start. Thanks for doing it!

This part concerns me:

Note: Since clojure maps are no java maps, they can't be equal to java
maps. This holds for wrapped clojure maps as well - surprisingly (in a
bad sense), but i don't see a way around this:

   (= (doto (HashMap.) (put 1 2)) {1 2})
   -> false

   (= (jmap {1 2}) {1 2})
   -> false

Here's my understanding of how = works in Clojure (corrections welcome):

Clojure defines "=" (for all types) as "has the same value". For two maps A and B (of any concrete type) this means:

For every key in A there exists a key in B that is = to it.
For every key in B there exists a key in A that is = to it.
For every key k in A, the value associated with k in A is = to the value associated with k in B

The map types that we have now implement this. I think it's very important that jmap fit in with that definition of "=".

What prevents jmap from participating in this same check? If there are more changes to existing Clojure code that are necessary to support this, I think they should be part of a jmap patch.

--Steve

Stephen C. Gilardi

unread,
Sep 29, 2008, 11:20:33 PM9/29/08
to clo...@googlegroups.com
On Sep 29, 2008, at 3:20 PM, Stephen C. Gilardi wrote:

This part concerns me:

Note: Since clojure maps are no java maps, they can't be equal to java
maps. This holds for wrapped clojure maps as well - surprisingly (in a
bad sense), but i don't see a way around this:

   (= (doto (HashMap.) (put 1 2)) {1 2})
   -> false

   (= (jmap {1 2}) {1 2})
   -> false

After some more thought, I'm not sure whether this should be a concern or not. Since jmap's purpose is to wrap Clojure maps for passing to Java APIs, I'm not sure when it will be necessary to compare jmaps to Clojure maps. In the abstract, it would be nice if they could be compared for equality, but I'm not sure how important this is in practice.

--Steve

Achim Passen

unread,
Sep 30, 2008, 4:56:37 AM9/30/08
to Clojure
Hi Steve,

thank you for taking a look and for your suggestions!

> This part concerns me:
>
> > Note: Since clojure maps are no java maps, they can't be equal to java
> > maps. This holds for wrapped clojure maps as well - surprisingly (in a
> > bad sense), but i don't see a way around this:
>
> > (= (doto (HashMap.) (put 1 2)) {1 2})
> > -> false
>
> > (= (jmap {1 2}) {1 2})
> > -> false

That bugs me too, and i did try to resolve it. As far as i can see,
there are only two ways out, neither of which i like very much (worst
first):

1. Break the transitivity of = (for cases where you wouldn't expect it
to break)
2. Change = to (= a b) not necessarily being equivalent to a.equals(b)

re 1: Basically, (= a b) maps to a.equals(b) in Java (except for nil
and numbers). Suppose we'd like it to stay that way. Implementing =
for jmaps and Clojure maps would of course be easy and just a matter
of extending their equals-methods accordingly (we'd like = to be
symmetric, which means "mutual agreement" on equality in Java).
Suppose we had three representations of the same mapping: a Clojure
map a, a jmap-wrapped Clojure map b and a plain Java map c. Of course,
we want (= b c), that's the point of the whole undertaking. We also
have (= a b) after augmenting the equals-methods in question, but we
can't possibly have (= c a), because a Java map won't agree on being
equal to anything else but a Java map.

(I realise that, for arbitrary Java objects and funny implementations
of equals, = is anything but an equivalence relation, but here it
always breaks, even if equals is defined sensibly)

re 2: The idea would be to check whether we're comparing a mix of
Clojure maps and Java maps, if so, compare them entry-wise (along the
lines of the definition you gave), disregarding the Java maps
"equals". I think this would lead to weird equality semantics (e.g. a
Clojure map would appear = to a Java map within Clojure, but not from
the Java point of view, thus you couldn't pass it to Java methods that
expect Java maps).

Either way we turn it, it looks like the distinction between Clojure
maps and Java maps can only be blurred, but not removed in a
consistent way. The examples above are somewhat factitious, but
they'll likely lead to quite real issues that will be hard to
understand/explain/debug.

So i'd like to argue for a clean cut instead of blurring. In that
sense, jmap is just a function that returns a Java object, not a
Clojure map type. :-) I considered not supporting conj, assoc and
invoking on them to avoid creating that outward expression, but i went
for practicality here.

Kind regards,
achim

Achim Passen

unread,
Sep 30, 2008, 5:04:48 AM9/30/08
to Clojure
On Sep 30, 5:20 am, "Stephen C. Gilardi" <squee...@mac.com> wrote:

> After some more thought, I'm not sure whether this should be a concern  
> or not. Since jmap's purpose is to wrap Clojure maps for passing to  
> Java APIs, I'm not sure when it will be necessary to compare jmaps to  
> Clojure maps. In the abstract, it would be nice if they could be  
> compared for equality, but I'm not sure how important this is in  
> practice.

I tend to agree. In most cases there should be no doubts whether a map
at hand is wrapped or not. If otherwise, you could safely unwrap both
via cmap (that's the reason why i wrote cmap to act "forgivingly" when
not applied to jmaps) and then compare afterwards.

Kind regards,
achim

Rich Hickey

unread,
Sep 30, 2008, 11:17:22 AM9/30/08
to Clojure
I've been thinking about this Map vs Collection issue for a while now
and am considering making the move to: Clojure maps implement Java
Map.

The jmap workaround highlights the awkwardness of not aligning with
Java here.

My current thoughts are:

- Java Maps not being Java Collections is truly sad, but not something
I can do anything about.

- For consumption from the Clojure side, I can hide that difference in
coll?, count, to-array etc.

- Code that looks for Collection currently matches Clojure maps but
wouldn't anymore. It never matched Java Maps. clojure code should move
to coll?, and for Java code I can provide RT.isCollection for
replacing instanceof calls.

- Using the ad hoc hierarchy support, I can unify Collection and Map
by deriving both from :Collection, thus allowing matches for all
collections in multimethods etc.

- The real reason to implement Java interfaces is to support use from
Java, where the limitations of Maps not being Collections is already a
fact of life, and Clojure maps not implementing Map a problem.

- If I'm going to do this, the sooner the better.

Any change in this area may involve some breakage, but I'm confident
there will be all necessary facilities to move existing code.

Feedback welcome,

Rich

Paul Barry

unread,
Sep 30, 2008, 11:20:02 AM9/30/08
to Clojure
On Sep 30, 11:17 am, Rich Hickey <richhic...@gmail.com> wrote:
> I've been thinking about this Map vs Collection issue for a while now
> and am considering making the move to: Clojure maps implement Java
> Map.

Yes, please do! +1!

Achim Passen

unread,
Sep 30, 2008, 12:21:03 PM9/30/08
to clo...@googlegroups.com

Yes, that’s much better than wrapping. I’m all for it!
(I assumed that collection-ness of clojure’s maps was set in stone,
otherwise i wouldn’t have suggested this change.)

Stuart Sierra

unread,
Oct 1, 2008, 10:08:34 AM10/1/08
to Clojure
On Sep 30, 11:17 am, Rich Hickey <richhic...@gmail.com> wrote:
> Feedback welcome,

Yes, please let Clojure maps implement java.util.Map. As long as seq
and coll? still work on maps, I think the breakage will be minimal.

-Stuart Sierra

Rich Hickey

unread,
Oct 6, 2008, 9:28:46 AM10/6/08
to Clojure
I've made maps implement java.util.Map
unified equality and hashCode semantics for sets/maps/lists with
java.util
enabled sort on all colls and strings
enabled to-array on Maps

In SVN rev 1052

Rich
Reply all
Reply to author
Forward
0 new messages