So far as I've seen, Clojure's core functions almost always work for different types of collection, e.g. conj
, first
, rest
, etc. I'm a little puzzled why disj
and dissoc
are different though; they have the exact same signature:
(dissoc map) (dissoc map key) (dissoc map key & ks)
(disj set) (disj set key) (disj set key & ks)
and fairly similar semantics. Why aren't these both covered by the same function? The only argument I can see in favor of this is that maps have both (assoc map key val)
and (conj map [key val])
to add entries, while sets only support (conj set k)
.
I can write a one-line function to handle this situation, but Clojure is so elegant so much of the time that it's really jarring to me whenever it isn't :)
Just to provide a counterpoise to Arthur's answer: conj
is defined even earlier (the name conj
appears on line 82 of core.clj vs.1443 for disj
and 1429 for dissoc
) and yet works on all Clojure collection types. :-) Clearly it doesn't use protocols – instead it uses a regular Java interface, as do most Clojure functions (in fact I believe that currently the only piece of "core" functionality in Clojure that uses protocols is reduce
/ reduce-kv
).
I'd conjecture that it's due to an aesthetic choice, and indeed probably related to the way in which maps support conj
– were they to support disj
, one might expect it to take the same arguments that could be passed to conj
, which would be problematic:
;; hypothetical disj on map
(disj {:foo 1
[:foo 1] 2
{:foo 1 [:foo 1] 2} 3}
}
{:foo 1 [:foo 1] 2} ;; [:foo 1] similarly problematic
)
Should that return {}
, {:foo 1 [:foo 1] 2}
or {{:foo 1 [:foo 1] 2} 3}
? conj
happily accepts [:foo 1]
or {:foo 1 [:foo 1] 2}
as things to conj
on to a map. (conj
with two map arguments means merge
; indeed merge
is implemented in terms of conj
, adding special handling of nil
).
So perhaps it makes sense to have dissoc
for maps so that it's clear that it removes a key and not "something that could be conj
'd".
Now, theoretically dissoc
could be made to work on sets, but then perhaps one might expect them to also support assoc
, which arguably wouldn't really make sense. It might be worth pointing out that vectors do support assoc
and not dissoc
, so these don't always go together; there's certainly some aesthetic tension here.
It's always dubious to try to answer for the motivations of others, though I strongly suspect this is a bootstrapping issue in core.clj. both of these functions are defined fairly early in core.clj and are nearly identical except that they each take exactly one type and call a method on it directly.
(. clojure.lang.RT (dissoc map key))
and
(. set (disjoin key))
both of these functions are defined before protocals are defined in core.clj
so they can't use a protocol to dispatch between them based on type. Both of these where also defined in the language specification before protocols existed. They are also both called often enough that there would be a strong incentive to make them as fast as possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With