"closed" maps / reducing runtime errors due to mistyped keywords

54 views
Skip to first unread message

Christian Schuhegger

unread,
Apr 22, 2011, 1:44:46 AM4/22/11
to Clojure
I am taking up a discussion from 2010:
https://groups.google.com/group/clojure/browse_frm/thread/60dff89149c3d2e6/

I would prefer if it would be possible to define "closed maps", e.g.
maps that allow only a certain set of keywords, both for get and
"set" (like in assoc, assoc-in, update-in, ...). It would be nice if
there would be an option on defrecord to mark the map as "closed".

I imagine that this would have some runtime penalty, because for every
access it has to be verified if the key is a valid one. I understand
that. Perhaps in that case the language could react differently based
on the value of a dynamic var (*check-closed-defrecrod-p*) that can be
set when the unit tests are run and could be false by default.

I guess this topic was already discussed several times. Could somebody
point me to previous discussions and their outcome?
I've found in addition the following thread where the idea of "safe
maps" was discussed to some extent:
https://groups.google.com/group/clojure/browse_frm/thread/134642cc76de17f7/

Thanks,
Christian

Justin Kramer

unread,
Apr 22, 2011, 11:33:28 AM4/22/11
to Clojure
I should be straightforward to implement a closed map (or record)
yourself using deftype. It could implement all the same interfaces as
Clojure's built-in maps, ensuring compatibility with assoc and such.
Here's an example of a map variant implemented using deftype:

https://github.com/clojure/clojure-contrib/blob/master/modules/priority-map/src/main/clojure/clojure/contrib/priority_map.clj

Justin

On Apr 22, 1:44 am, Christian Schuhegger
<christian.schuheg...@gmail.com> wrote:
> I am taking up a discussion from 2010:https://groups.google.com/group/clojure/browse_frm/thread/60dff89149c...
>
> I would prefer if it would be possible to define "closed maps", e.g.
> maps that allow only a certain set of keywords, both for get and
> "set" (like in assoc, assoc-in, update-in, ...). It would be nice if
> there would be an option on defrecord to mark the map as "closed".
>
> I imagine that this would have some runtime penalty, because for every
> access it has to be verified if the key is a valid one. I understand
> that. Perhaps in that case the language could react differently based
> on the value of a dynamic var (*check-closed-defrecrod-p*) that can be
> set when the unit tests are run and could be false by default.
>
> I guess this topic was already discussed several times. Could somebody
> point me to previous discussions and their outcome?
> I've found in addition the following thread where the idea of "safe
> maps" was discussed to some extent:https://groups.google.com/group/clojure/browse_frm/thread/134642cc76d...
>
> Thanks,
> Christian

Armando Blancas

unread,
Apr 22, 2011, 11:47:40 AM4/22/11
to Clojure
With regard to mistyped keys in general, a simple option is to use
named keys; then the compiler will flag undefined ones.
(def k :key)
...
(k m)

On Apr 21, 10:44 pm, Christian Schuhegger
<christian.schuheg...@gmail.com> wrote:
> I am taking up a discussion from 2010:https://groups.google.com/group/clojure/browse_frm/thread/60dff89149c...
>
> I would prefer if it would be possible to define "closed maps", e.g.
> maps that allow only a certain set of keywords, both for get and
> "set" (like in assoc, assoc-in, update-in, ...). It would be nice if
> there would be an option on defrecord to mark the map as "closed".
>
> I imagine that this would have some runtime penalty, because for every
> access it has to be verified if the key is a valid one. I understand
> that. Perhaps in that case the language could react differently based
> on the value of a dynamic var (*check-closed-defrecrod-p*) that can be
> set when the unit tests are run and could be false by default.
>
> I guess this topic was already discussed several times. Could somebody
> point me to previous discussions and their outcome?
> I've found in addition the following thread where the idea of "safe
> maps" was discussed to some extent:https://groups.google.com/group/clojure/browse_frm/thread/134642cc76d...
>
> Thanks,
> Christian

Alex Miller

unread,
Apr 22, 2011, 8:52:53 PM4/22/11
to Clojure
This defrecord2 implementation has key checks on construction but
still allows you to forcibly insert other keys after the fact. I
believe the proposed defrecord enhancements for 1.3 may have some
similar opportunities. You could use your own assoc that checked the
known record keys too.

https://github.com/david-mcneil/defrecord2


On Apr 22, 10:47 am, Armando Blancas <armando_blan...@yahoo.com>
wrote:

Christian Schuhegger

unread,
Apr 23, 2011, 2:57:04 AM4/23/11
to Clojure
Thanks for all of your answers, but I think for my use cases the
approach proposed by Justin looks most promising. I'll implement my
own closed map.

Another idea that come to my mind was that a closed map is actually a
Java Bean without behaviour. Perhaps the closed map could be
implemented by dynamically creating an immutable bean at run-time
somehow along the lines of this article:
http://blogs.sun.com/adventures/entry/dynamic_java_beans

The advantage would be that other JVM languages could more easily take
advantage of such objects, e.g. rule engines like drools or complex
even processing engines like esper.

Are there any disadvantages you would see?

I would imagine that clojure's compiler already has most of what is
needed for such an approach. Could somebody give me a good entry point
where to start my analysis of the clojure internals to create
dynamically beans at runtime?

In the past clojure seems to have had a gen-and-load-class feature,
which disappeared. I could not find references that would explain the
reasons why it was taken out.
The following post from 2008 basically would already do what I have in
mind but it uses the gen-and-load-class feature:
http://groups.google.com/group/clojure/browse_thread/thread/79e4cb66a05c8ce9

pepijn (aka fliebel)

unread,
Apr 23, 2011, 3:51:32 AM4/23/11
to Clojure
This project seems to be all about beans, but it is empty so far:
https://github.com/clojure/java.data

On Apr 23, 8:57 am, Christian Schuhegger
> mind but it uses the gen-and-load-class feature:http://groups.google.com/group/clojure/browse_thread/thread/79e4cb66a...

Steve Miner

unread,
Apr 26, 2011, 1:06:39 PM4/26/11
to clo...@googlegroups.com
Creating your own "Closed Map" type would be the object-oriented approach. One downside is that a closed-map feels like a normal map, but can't be safely substituted for most maps because of the booby-trap when using assoc (etc.) with a new key. It doesn't really fulfill the map contract anymore. You'll have to do some defensive copying if you need a normal map.

I would like to suggest a functional approach as an alternative. It seems to me that the concept of "closedness" is a matter of interpretation. It could be determined by the functions manipulating the data. Sometimes you might want to treat the data (map) as closed and other times you might not care, so you just need to use the appropriate functions.

If you create your closed maps with all the allowed keys (nil values as appropriate), you just need to call contains? on any new key to make sure it's allowed. You could do that in a pre-condition. For example:

(defn assert-key [m k] {:pre [(contains? m k)]} k)

(defn assoc-closed [m k v]
(assoc m (assert-key m k) v))

(defn get-closed [m k]
(get m (assert-key m k)))


Steve Miner
steve...@gmail.com

Ken Wesson

unread,
Apr 26, 2011, 2:26:59 PM4/26/11
to clo...@googlegroups.com

+1

Christian Schuhegger

unread,
Apr 27, 2011, 9:00:18 AM4/27/11
to Clojure
Thanks for that recommendation, I definitely like that proposal!
I guess the drawback is then that destructuring will not be "safe".
I have to think some more time about it.
Reply all
Reply to author
Forward
0 new messages