On Aug 5, 2012, at 2:56 AM, Sean Corfield wrote:
> On Sat, Aug 4, 2012 at 11:45 PM, Mark Engelberg
> <[email protected]> wrote:
>> In any case, Clojure is already able to detect when x and y are equal in
>> something like #{x y} and report it as an error.
>
> So do you think #{1 1} should not be an error? And {:a 1 :a 2}? These
> seem like "obvious" programmer errors that I'd want the compiler to
> catch.
I hit exactly the problem the OP raised this past week. I knew of the (good)
restriction on e.g. #{1 1} being an error, and remember some of the
conversations leading up to the changes that made it so, but had either
forgotten or never quite internalized that #{a b} is also an error, where a and
b are equivalent values.
First, some history:
Looks like
http://groups.google.com/group/clojure/browse_thread/thread/5a38a6b61b09e025
was the original thread where duplicate map keys first came up as an issue,
although the differences between array-maps and hash-maps were the original
impetus.
This led to http://dev.clojure.org/jira/browse/CLJ-87 being filed
Rich's first change was to make duplicate map keys an error, regardless of
whether the values in question were literals themselves or evaluated from an
expression:
https://github.com/clojure/clojure/commit/e6e39d5931fbdf3dfa68cd2d059b8e26ce45c965
A brief discussion in irc ensued —
http://clojure-log.n01se.net/date/2010-04-05.html#10:56a — where Rich suggested
that sets should probably be subject to the same rules as keys of maps (which,
of course they should be, whatever those rules may be, since a map's keys are
always a set).
The final commit on the issue extended the error-checking of map keys to set
values:
https://github.com/clojure/clojure/commit/c733148ba0fb3ff7bbab133f5375422972e62d08
Note that the .createWithCheck variations of all of the collections in question
are used by their "constructor" functions as well, e.g. hash-set, hash-map, and
array-map:
=> (hash-set 1 2 2)
IllegalArgumentException Duplicate key: 2
clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:80)
=> (hash-map 1 2 1 3)
IllegalArgumentException Duplicate key: 1
clojure.lang.PersistentHashMap.createWithCheck (PersistentHashMap.java:92)
=> (array-map 1 2 1 3)
IllegalArgumentException Duplicate key: 1
clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
The only way to get around the checks here is to use `set` or `into`; note that
there is no "constructor" function for an unsorted map that does not check that
the provided keys are unique.
Interestingly, sorted maps and sets do *not* have the same restriction:
=> (sorted-map 1 2 1 3)
{1 3}
=> (sorted-set 1 2 1)
#{1 2}
Quoting Rich from the mailing list thread linked above:
> These are bugs in user code. Map literals are in fact read as maps, so
> a literal map with duplicate keys isn't going to produce an evaluated
> map with distinct keys. If you create an array map with duplicate
> keys, bad things will happen.
In the end, even though I've been recently "bitten" by the checked creation of
sets from a literal, I think it's a reasonable approach. In #{a b}, you are
specifying the creation of a set containing two and exactly two values, those
named by a and b. There's an explicit invariant being specified in that code.
Thinking over my various uses of #{} syntax, I remember times where I've
expected it to enforce that invariant (and throw an error if dupes were
provided) and times where I've expected it to implicitly apply `distinct` to
the values provided; that indicates sloppiness on my part, not a place where
the language should become psychic.
In contrast, `set` and `into` each accept a seqable collection of data, and are
explicit about their support for sifting out duplicates (right in the docstring
of `set`, and transitively so for `into` due to its use of `conj` and its
semantics on sets).
Finally, for the sake of consistency, it seems like the same checks should be
applied by the sorted map and set "constructor" functions, and that there
should be a map corollary to `set` (i.e. a function that is the equivalent of
#(into {} %), just as `set` is the equivalent of #(into #{} %)). This last one
is problematic in terms of naming, though.
Cheers,
- Chas
--
http://cemerick.com
[Clojure Programming from O'Reilly](http://www.clojurebook.com)
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en