13

I am a little confused by the clojure instance? function. It seems quite happy to take a single argument. So

(instance? String) 

works fine, but always returns false.

Am I missing something here? I've done this twice in two days, and both times it took me a quite a long time to debug (yes, I agree, to make the mistake once might be regarded as misfortune, but twice looks like carelessness).

Why doesn't it break, with an arity error?

Note added later: As of Clojure 1.6 this has been fixed!

http://dev.clojure.org/jira/browse/CLJ-1171

Phil Lord
  • 2,917
  • 1
  • 20
  • 31

4 Answers4

8

Interesting... even though instance? is defined in core.clj, it appears that there is special handling built in to clojure.lang.Compiler for (instance?) forms.

Compiler.java, line 3498:

    if(fexpr instanceof VarExpr && ((VarExpr)fexpr).var.equals(INSTANCE))
        {
        if(RT.second(form) instanceof Symbol)
            {
            Class c = HostExpr.maybeClass(RT.second(form),false);
            if(c != null)
                return new InstanceOfExpr(c, analyze(context, RT.third(form)));
            }
        }

I interpret that to mean that, when you compile/evaluate an (instance?) form, the function defined in core.clj is ignored in favor of the hard-wired behavior, which does interpret a missing second argument as nil. I'm guessing this is done for performance reasons, as a sort of in-lining.

Apparently this special handling only applies in certain cases (and I'm not familiar enough with the compiler to know what they are). As illustrated by Ankur's answer, there are ways of calling instance? that cause the function defined in core.clj to be invoked.

Alex
  • 13,811
  • 1
  • 37
  • 50
  • The fast path in the compiler (which emits an instanceof expression) is taken when the call is a direct call with a symbol that directly denotes a class. – Bendlas Feb 27 '13 at 19:04
3

I think it is a bug. If you define a new version of instance?, e.g.

(def
^{:arglists '([^Class c x])
  :doc "Evaluates x and tests if it is an instance of the class
        c. Returns true or false"
  :added "1.0"}
foo? (fn foo? [^Class c x] (. c (isInstance x))))

you will get the expected exception

user=> (foo? String "bar")
true
user=> (foo? String 1)
false
user=> (foo? String)
ArityException Wrong number of args (1) passed to: user$foo-QMARK-  clojure.lang.AFn.throwArity (AFn.java:437)
Joe Lehmann
  • 1,087
  • 5
  • 10
2

If you look at the instance? code you will see that the method isInstance of Class is called:

(def
    ^{:arglists '([^Class c x])
      :doc "Evaluates x and tests if it is an instance of the class
            c. Returns true or false"
      :added "1.0"}
    instance? (fn instance? [^Class c x] (. c (isInstance x))))

Looks like under the hood, nil (or false) is considered as the default value for x parameter when passed to the isInstance and that returns false.

Giuliani Sanches
  • 650
  • 5
  • 20
  • 1
    This alone fails to explain the observed behavior, because other functions defined in this manner do throw arity exceptions when called with the incorrect number of args. – Alex Nov 01 '12 at 17:17
  • You're right. I have not tested with my own version of this function. My bad. And looks like this "issue" persist since Clojure 1.3: https://groups.google.com/forum/?fromgroups=#!topic/clojure-dev/OmmqMyd_uXE – Giuliani Sanches Nov 01 '12 at 19:57
  • Interesting. I searched for a ticket that was suggested by this thread, and couldn't find it. I guess it has fallen under the wire. I'll see if I can work out how to submit a new bug report. Failing this, I guess I will wrap instance? for now. – Phil Lord Nov 01 '12 at 21:35
2

Hmm....interesting... all the below calls fails (which is how it is supposed to be):

user=> (.invoke instance? String)
ArityException Wrong number of args (1) passed to: core$instance-QMARK-  clojure.lang.AFn.throwArity (AFn.java:437)

user=> (instance? (type ""))
ArityException Wrong number of args (1) passed to: core$instance-QMARK-  clojure.lang.AFn.throwArity (AFn.java:437)

user=> (apply instance? String [])
ArityException Wrong number of args (1) passed to: core$instance-QMARK-  clojure.lang.AFn.throwArity (AFn.java:437)

user=> (#'instance? Long)
ArityException Wrong number of args (1) passed to: core$instance-QMARK-  clojure.lang.AFn.throwArity (AFn.java:437)

Event creating a new instance of "instance?" function object works as it is supposed to work:

user=> (def a (.newInstance (aget (.getConstructors (type instance?)) 0) (into-array [])))
#'user/a
user=> (a String)
ArityException Wrong number of args (1) passed to: core$instance-QMARK-  clojure.lang.AFn.throwArity (AFn.java:437)
user=> (a String "")
true
Ankur
  • 33,367
  • 2
  • 46
  • 72