Proposal: Extend behavior of hash-map

87 views
Skip to first unread message

Sean Devlin

unread,
Nov 17, 2009, 4:46:12 PM11/17/09
to Clojure
Hey everyone,
I was working with an object that implements java.util.Map today, and
I had to turn it into a Clojure map.

http://java.sun.com/j2se/1.5.0/docs/api/java/util/Map.html

I cam up with this utility fn

(defn read-map
"Designed to turn a java.util.Map into a Clojure map."
[a-map]
(into {}
(map
#(hash-map (.getKey %) (.getValue %))
(seq a-map))))

What if hash-map automatically cast a java.util.Map to a Clojure map?
Here's a proposed hash-map*

(defn hash-map*
([] (hash-map))
([& keyvals]
(if (= (count keyvals) 1)
(if (instance? java.util.Map (first keyvals))
(read-map (first keyvals))
(apply hash-map keyvals)) ;throws proper exception
(apply hash-map keyvals))))

Is this auto-casting a desired behavior? If not, does anyone want
read-map in contrib (c.c.map-utils)?

Sean

Richard Newman

unread,
Nov 17, 2009, 4:56:01 PM11/17/09
to clo...@googlegroups.com
Sean,

If the class implements Map, then it already behaves as an associative
data structure in Clojure. E.g.,

(map (fn [[k v]] (println v))
(doto (java.util.HashMap.)
(.put "foo" "bar")
(.put "baz" "noo")))

(get (doto (java.util.HashMap.)
(.put "foo" "bar")
(.put "baz" "noo")) "foo")
=> "bar"

That means you can write read-map as

(defn read-map [m]
(merge {} m))

-R

Sean Devlin

unread,
Nov 17, 2009, 5:06:38 PM11/17/09
to Clojure
Heh. Learn something new every day.

This also works

(into {} (System/getProperties))

Kevin Downey

unread,
Nov 17, 2009, 4:59:17 PM11/17/09
to clo...@googlegroups.com
user=> (import 'java.util.HashMap)
java.util.HashMap
user=> (def m (doto (HashMap.) (.put 'a :a) (.put 'b :b)))
#'user/m
user=> m
#<HashMap {b=:b, a=:a}>
user=> (into {} m)
{b :b, a :a}
user=> (class *1)
clojure.lang.PersistentArrayMap
user=>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en



--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

Richard Newman

unread,
Nov 17, 2009, 5:09:55 PM11/17/09
to clo...@googlegroups.com
> Heh. Learn something new every day.

Heh.

Incidentally, because of this property there are only three situations
in which you need conversion at all:

* To get some additional interface that Clojure's maps provides (e.g.,
to use them as functions)
* To get persistence
* To have them print readably.

To just get values out, or walk the map, you don't need to do the
conversion.

Alex Osborne

unread,
Nov 17, 2009, 5:12:25 PM11/17/09
to clo...@googlegroups.com
Sean Devlin wrote:
> Hey everyone,
> I was working with an object that implements java.util.Map today, and
> I had to turn it into a Clojure map.
>
> http://java.sun.com/j2se/1.5.0/docs/api/java/util/Map.html
>
> I cam up with this utility fn
>
> (defn read-map
> "Designed to turn a java.util.Map into a Clojure map."
> [a-map]
> (into {}
> (map
> #(hash-map (.getKey %) (.getValue %))
> (seq a-map))))

Why the (map) and seq? Does (into {} a-map) not work for some
implementations java.util.Map?

(into {}
(doto (java.util.HashMap.)
(.put :a 1)
(.put :b 2)))
;; => {:a 1, :b 2}

> What if hash-map automatically cast a java.util.Map to a Clojure map?

That'd really be changing the meaning of (hash-map) -- it doesn't even
do that with Clojure maps, you can't do (hash-map {:a 1}).
Better to stick with (into {}) -- it's actually both one character
shorter and more generally applicable.

Sean Devlin

unread,
Nov 17, 2009, 5:14:40 PM11/17/09
to Clojure
Wow.

So this works

user=>(get (System/getProperties) "os.arch")
"x86_64"

And this does not

user=>((System/getProperties) "os.arch")
java.lang.ClassCastException: java.util.Properties cannot be cast to
clojure.lang.IFn (NO_SOURCE_FILE:0)

but this does

user=>((into {} (System/getProperties)) "os.arch")
"x86_64"

Just when I thought I knew the API... wonder what else is out there?
Sean

Sean Devlin

unread,
Nov 17, 2009, 5:27:38 PM11/17/09
to Clojure
Okay golfers. Is there a better way to do this?

(defn put-all!
[java-map clj-map]
(do
(doseq [entry clj-map]
(.put java-map (key entry) (val entry)))
java-map))

user=>(put-all (java.util.HashMap. ) {:a 1 :b 2 :c 3})
#<HashMap {:c=3, :b=2, :a=1}>

I already tried into :)

Alex Osborne

unread,
Nov 17, 2009, 5:29:53 PM11/17/09
to clo...@googlegroups.com
Sean Devlin wrote:
> Okay golfers. Is there a better way to do this?
>
> (defn put-all!
> [java-map clj-map]
> (do
> (doseq [entry clj-map]
> (.put java-map (key entry) (val entry)))
> java-map))
>
> user=>(put-all (java.util.HashMap. ) {:a 1 :b 2 :c 3})
> #<HashMap {:c=3, :b=2, :a=1}>
>
> I already tried into :)

(java.util.HashMap. {:a 1 :b 2 :c 3})
=> #<HashMap {:c=3, :b=2, :a=1}>

Clojure maps implement java.util.Map so you can use them where'd you'd
(immutably) use most java maps.

Mark Triggs

unread,
Nov 17, 2009, 5:31:12 PM11/17/09
to clo...@googlegroups.com
How about?:

(java.util.HashMap. {:a 1 :b 2 :c 3})

=> #<HashMap {:c=3, :b=2, :a=1}>


Mark

Sean Devlin <francoi...@gmail.com> writes:

> Okay golfers. Is there a better way to do this?
>
> (defn put-all!
> [java-map clj-map]
> (do
> (doseq [entry clj-map]
> (.put java-map (key entry) (val entry)))
> java-map))
>
> user=>(put-all (java.util.HashMap. ) {:a 1 :b 2 :c 3})
> #<HashMap {:c=3, :b=2, :a=1}>
>
> I already tried into :)

--
Mark Triggs
<mark.h...@gmail.com>

Alex Osborne

unread,
Nov 17, 2009, 5:33:58 PM11/17/09
to clo...@googlegroups.com
Although, perhaps what you were looking for is:

(doto (java.util.HashMap.)
(.putAll {:a 1 :b 2 :c 3}))

Sean Devlin

unread,
Nov 17, 2009, 5:36:31 PM11/17/09
to Clojure
Very awesome. So this means you can (almost) use the putAll method.
The only catch is putAll return void (WHY!)

(defn put-all
[java-map clj-map]
(doto java-map
(.putAll clj-map)))

Sean

Richard Newman

unread,
Nov 17, 2009, 5:40:17 PM11/17/09
to clo...@googlegroups.com
> The only catch is putAll return void (WHY!)

Apparently Java people like having more LOC :)

> (defn put-all
> [java-map clj-map]
> (doto java-map
> (.putAll clj-map)))

Note that this isn't limited to Clojure maps, so 'clj-map' is probably
a bad variable name. It works just as well for combining two Java maps.

(import 'java.util.Map 'java.util.HashMap)

(defn put-all
[#^Map java-map #^Map clj-map]
(doto java-map
(.putAll clj-map)))


user=> (put-all (HashMap. {:foo :bar}) (HashMap. {:bar :baz}))
#<HashMap {:bar=:baz, :foo=:bar}>

John Harrop

unread,
Nov 17, 2009, 6:01:24 PM11/17/09
to clo...@googlegroups.com
On Tue, Nov 17, 2009 at 5:06 PM, Sean Devlin <francoi...@gmail.com> wrote:
Heh.  Learn something new every day.

This also works

(into {} (System/getProperties))

And I'd much prefer it. Passing a mutable Java map around to functions that expect a map but assume it will never change out from under them creates a grave risk that the map WILL be changed out from under them at some point.

I wonder if perhaps (into {} a-java-map) should work but no other substitutions of a potentially-mutable map for a Clojure map.

Richard Newman

unread,
Nov 17, 2009, 6:24:46 PM11/17/09
to clo...@googlegroups.com
> I wonder if perhaps (into {} a-java-map) should work but no other
> substitutions of a potentially-mutable map for a Clojure map.

Baby, bathwater. Making a persistent map out of a Java map is
expensive. Not everything that implements Map is concrete; e.g.,
spending several seconds making a local persistent Clojure map out of
a distributed hash table proxy, just to get a value, would cause
programmers to drop down to Java to avoid this pointless restriction.
Why bother?

Shooting at targets gives you the opportunity to shoot yourself in the
foot, and that's not a bad thing.

If you want the guarantees of persistence, then by all means use
persistent structures. Programming is the act of making these choices.

John Harrop

unread,
Nov 17, 2009, 7:05:48 PM11/17/09
to clo...@googlegroups.com
On Tue, Nov 17, 2009 at 6:24 PM, Richard Newman <holy...@gmail.com> wrote:
> I wonder if perhaps (into {} a-java-map) should work but no other
> substitutions of a potentially-mutable map for a Clojure map.

Baby, bathwater. Making a persistent map out of a Java map is
expensive. Not everything that implements Map is concrete; e.g.,
spending several seconds making a local persistent Clojure map out of
a distributed hash table proxy, just to get a value, would cause
programmers to drop down to Java to avoid this pointless restriction.
Why bother?

Better let "get" still work on them too, then. :) 

David Brown

unread,
Nov 18, 2009, 4:23:52 AM11/18/09
to clo...@googlegroups.com
On Tue, Nov 17, 2009 at 03:24:46PM -0800, Richard Newman wrote:

>Baby, bathwater. Making a persistent map out of a Java map is
>expensive. Not everything that implements Map is concrete; e.g.,
>spending several seconds making a local persistent Clojure map out of
>a distributed hash table proxy, just to get a value, would cause
>programmers to drop down to Java to avoid this pointless restriction.
>Why bother?

I wonder if there's a use for a lazy 'bean' call, then. Lots of
things use bean "properties" to do things that can be quite expensive.

David

Sean Devlin

unread,
Nov 18, 2009, 9:06:52 AM11/18/09
to Clojure
Do you mean the bean fn?

http://clojure.org/api#toc120

David Brown

unread,
Nov 19, 2009, 12:45:04 AM11/19/09
to clo...@googlegroups.com
On Wed, Nov 18, 2009 at 06:06:52AM -0800, Sean Devlin wrote:
>Do you mean the bean fn?
>
>http://clojure.org/api#toc120

This does appear to be lazy, although only partially. Things like
(keys mybean) invokes the beans, but I think that's more just a
consequence of how iterators work in Java/Clojure.

Nice to see that it doesn't make the calls until asked for.
Reply all
Reply to author
Forward
0 new messages