You know what ({:foo 1} :foo)
, (:foo {:foo 1})
, and (#{:foo}
:foo)
evaluate to , but what about (:foo #{:foo})
?
Answer:
Here a keyword is being used as a function. Is that legal?
(isa? (type :foo) clojure.lang.IFn)
;; => true
Yes, a keyword is a function. Here’s what defines what happens when a keyword is used as a function:
// https://github.com/clojure/clojure/blob/clojure-1.9.0/src/jvm/clojure/lang/Keyword.java#L137
final public Object invoke(Object obj) {
if(obj instanceof ILookup)
return ((ILookup)obj).valAt(this);
return RT.get(obj, this);
}
Is #{:foo}
an instance of ILookup
?
(isa? (type #{:foo}) clojure.lang.ILookup)
;; => false
It is not, so find what RT.get(obj, this)
does when obj
is
#{:foo}
and this
is :foo
:
// https://github.com/clojure/clojure/blob/clojure-1.9.0/src/jvm/clojure/lang/RT.java#L751
static public Object get(Object coll, Object key){
if(coll instanceof ILookup)
return ((ILookup) coll).valAt(key);
return getFrom(coll, key);
}
static Object getFrom(Object coll, Object key){
if(coll == null)
return null;
else if(coll instanceof Map) {
Map m = (Map) coll;
return m.get(key);
}
else if(coll instanceof IPersistentSet) {
IPersistentSet set = (IPersistentSet) coll;
return set.get(key);
}
else if(key instanceof Number && (coll instanceof String || coll.getClass().isArray())) {
int n = ((Number) key).intValue();
if(n >= 0 && n < count(coll))
return nth(coll, n);
return null;
}
else if(coll instanceof ITransientSet) {
ITransientSet set = (ITransientSet) coll;
return set.get(key);
}
return null;
}
Once again, #{:foo}
is not an ILookup
, so getFrom
is
called. coll
is #{:foo}
, which is not null
, nor is it an
instance of Map
. It is an instance of
IPersistentSet
.
The implementation of set.get
is in APersistentSet
because
(isa? (type #{:foo}) clojure.lang.APersistentSet)
;; => true
and ~APersistentSet~ has an implementation of ~get~:
public Object get(Object key){
return impl.valAt(key);
}
An instance of APersistentSet
has an ~IPersistentMap~, so the
above is rougly equivalent to the Clojure:
({:foo :foo} :foo)
;; => :foo
So (:foo #{:foo})
evaluates to :foo
.