Why does Clojure say "no suitable method" for an illegal argument?
Fixed use of / isWhitespace characters:
(Character/isWhitespace \a) => false (Character/isWhitespace \ ) => true However, my first attempt was this, and I find the mistake confusing.
(Character/isWhitespace "") => IllegalArgumentException No matching method found: isWhitespace => clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:80) The IllegalArgument part makes sense, but why does it say "no matching method found"? Obviously, the function exists.
Explanation
The reason I ask this question is because I am new to Clojure and I think that I fundamentally underestimate something.
When I type (Character/isWhitespace \a) , what I think , I say: "I know that there is a Character namespace, and inside it there is a function called isWhitespace , and I want to call this function and go to \a "
In this mental model, my results above are confusing because Clojure seems to say: "whenever you give me a type of argument that this function does not accept, I will pretend that the function does not exist." For example, "you are not allowed to mix bricks, so if you try, I will give you a BlenderDoesntExist error." Which is strange.
Some answers seem to imply that
- The name
Character/isWhitespaceis part of what Clojure uses to search for functions, and the other part is the type of argument. (I did a few more searches: maybe this is a multimethod?) - Method viewed in java class?
A great answer will clarify this process for me.
Character/isWhitespace is a Java method (static method of the java.lang.Character class). See Java interop for syntax examples that invoke Java methods.
While ordinary Clojure functions are defined only by their name, Java methods are defined by their signature , which consists of their name and number and types of their parameters.
The only variants of the isWhitespace method defined in the Character class are isWhitespace(char ch) and isWhitespace(int codepoint) . So there is no matching method for calling isWhitespace with a string.
TL; DR
The clojure compiler relies on reflection to find matching labels for Java interop class methods, and it throws its own exceptions when nothing is found.
In this case, an IllegalArgumentException is IllegalArgumentException , but the noMethodReport error message is noMethodReport , which leads to confusion.
And this is the source code in the clojure github registry for it .
Long version
The first is going through Java parsing.
When the clojure parser finds a macro . The HostExpr parser handles the parsing, which tries to decide if the second argument is a character or a class.
If it is a class, it accepts a static method of calling this class, and parsing continues on StaticMethodExpr .
The first thing inside the parser is trying to find the method by reflecting the class:
List methods = Reflector.getMethods(c, args.count(), methodName, true); if(methods.isEmpty()) throw new IllegalArgumentException("No matching method: " + methodName); That he finds correctly, and at this moment there is no exception
Then it adds parameters to the methods found:
java.lang.reflect.Method m = (java.lang.reflect.Method) methods.get(i); params.add(m.getParameterTypes()); After that, it tries to find the signature index of the matching method:
methodidx = getMatchingParams(methodName, params, args, rets); Which for this case returns '-1', and method remains zero. And this is for the parsing stage.
Evaluation Time ...
Then, when invokeStaticMethod is invokeStaticMethod , it calls getMethods on Reflector.java , which correctly finds two matching signatures for 'isWhitespace'.
And finally, the confusing message you see inside the function:
static Object invokeMatchingMethod(String methodName, List methods, Object target, Object[] args) The found methods are checked for parameter matching, trying to find a method with the corresponding signature:
for(Iterator i = methods.iterator(); i.hasNext();) { m = (Method) i.next(); Class[] params = m.getParameterTypes(); if(isCongruent(params, args)) If the counter signature is not found, an exception is thrown
if(m == null) throw new IllegalArgumentException(noMethodReport(methodName,target)); Thus, the answer will be that the clojure compiler relies on reflection to find the appropriate signatures for the methods, and it creates its own exceptions when nothing is found.
In this case, an IllegalArgumentException is IllegalArgumentException , but the noMethodReport error message is noMethodReport , which leads to confusion.
Because "" is a type string, not a character.
user=> (type \a) java.lang.Character user=> (type "") java.lang.String The string (java) class does not have the "isWhitespace" method, while the "character" does this. You cannot control the nail with a screwdriver.
There are a few hints:
How to represent an empty char in Java Character class
"is an empty string. But there is no idea of ββan" empty "character. Although the character" \ 0 "does:
user=> (Character/isWhitespace \0) false And a document like whitespace doc:
http://docs.oracle.com/javase/6/docs/api/java/lang/Character.html#isWhitespace%28char%29
Says that:
"The set of characters from U + 0000 to U + FFFF is sometimes called the base multilingual plane (BMP). Characters whose code points are larger than U + FFFF are called optional characters. The Java 2 platform uses UTF-16 in char arrays and in the String and StringBuffer classes . In this view, additional characters are represented as a pair of char values, the first from the range of high surrogates, (\ uD800- \ uDBFF), the second from the range of low surrogates (\ uDC00- \ uDFFF). " to which an empty quote does not fall.
In other words, the isWhitespace method does not have the ability to "understand" an empty string.
Also from clojure java interlop doc http://clojure.org/java_interop :
Preferred idiomatic forms for accessing fields or members of a method are given above. The instance member form works for both fields and methods. All of them expand to calls to the point operator (described below) at the time of macro expansion.
typing
(Character/isWhitespace "") expands to
(. Classname instanceMember instance args*) (. Character isWhitespace "") causes an error.
user=> (. Character isWhitespace "") java.lang.IllegalArgumentException: No matching method found: isWhitespace (NO_SOURCE_FILE:0) While you need to look at the clojure source code for confirmation, I assume that this comes from the dynamic compilation of java, which may be under it:
public class toto { public toto () { Character.isWhitespace(""); } } then
javac toto.class toto.java:5: error: no suitable method found for isWhitespace(String) Character.isWhitespace(""); ^ method Character.isWhitespace(int) is not applicable (actual argument String cannot be converted to int by method invocation conversion) method Character.isWhitespace(char) is not applicable (actual argument String cannot be converted to char by method invocation conversion) 1 error