【问题标题】:Why does Clojure say "no matching method" for an illegal argument?为什么 Clojure 对非法参数说“没有匹配方法”?
【发布时间】:2013-11-04 14:33:31
【问题描述】:

Character/isWhitespace 的正确用法包括:

(Character/isWhitespace \a) => false
(Character/isWhitespace \ ) => true

但是,我的第一次尝试是这样,我发现错误令人困惑。

(Character/isWhitespace "")
  =>  IllegalArgumentException No matching method found: isWhitespace
  => clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:80)

IllegalArgument 部分是有道理的,但为什么它说“找不到匹配方法”?显然该功能确实存在。

澄清

我问这个问题的原因是我是 Clojure 的新手,并且认为我从根本上误解了一些东西。

当我输入 (Character/isWhitespace \a) 时,我认为我的意思是:“我知道有一个 Character 命名空间,其中有一个名为 isWhitespace 的函数,我想要调用该函数并传入\a"。

在这个心智模型上,我上面的结果令人困惑,因为 Clojure 似乎在说,“只要你给我一个这个函数不接受的参数类型,我就会假装这个函数不存在。 "例如,“你不允许混合砖块,所以如果你尝试,我会给你一个 BlenderDoesntExist 错误。”这很奇怪。

有些答案似乎暗示了这一点

  • 名称Character/isWhitespace 只是Clojure 用于查找函数的一部分,另一部分是参数的类型。 (我进行了更多搜索:这可能是一种多方法吗?)
  • 正在查找 Java 类的方法?

一个很好的答案会为我阐明这个过程。

【问题讨论】:

  • 我猜是因为没有接受StringisWhitespace
  • 添加了编译器演练点宏解释过程,有没有清楚的告诉我
  • 命名空间/函数通常不以大写字母开头,也不拼写为驼峰式。您可以通过尝试在 repl 中将其用作独立值来区分真正的 clojure 函数和 java 方法 - 您将收到方法错误,对于函数,它将显示函数的内部表示。

标签: clojure


【解决方案1】:

tl;dr

clojure 编译器依赖反射来为 Java 互操作类方法找到匹配的签名,当找不到任何东西时,它会引发自己的异常。

在这种情况下,IllegalArgumentException 被正确引发,但 noMethodReport 错误消息会显示,这会导致混淆。

还有this is the source code responsible for itclojure github 存储库上。

长版

一、Java互操作解析演练。

当 clojure 解析器找到 . 点宏时,HostExpr 解析器会处理解析,尝试确定第二个参数是符号还是类。

如果它是一个类,则假定它是被调用的该类的静态方法,并在 StaticMethodExpr 上继续解析。

解析器内部的第一件事是尝试通过对类的反射来找到方法:

  List methods = Reflector.getMethods(c, args.count(), methodName, true);
  if(methods.isEmpty())
      throw new IllegalArgumentException("No matching method: " + methodName);

它正确找到并且此时没有引发异常

然后它将参数添加到找到的方法中:

  java.lang.reflect.Method m = (java.lang.reflect.Method) methods.get(i);
  params.add(m.getParameterTypes());

然后尝试找到匹配的方法签名索引:

  methodidx = getMatchingParams(methodName, params, args, rets);

在这种情况下返回“-1”并且method 保持为空。这就是解析阶段。

评估时间...

然后当调用invokeStaticMethod 时,它会在Reflector.java 上调用getMethods,从而正确地找到两个匹配的“isWhitespace”签名。

最后是你在函数内部看到的令人困惑的消息:

 static Object invokeMatchingMethod(String methodName, List methods, Object target, Object[] args)

对找到的方法进行参数匹配测试,试图找到具有正确签名的方法:

 for(Iterator i = methods.iterator(); i.hasNext();)
   {
    m = (Method) i.next();
    Class[] params = m.getParameterTypes();
    if(isCongruent(params, args))

如果没有找到匹配的签名,则会引发异常

if(m == null)
   throw new IllegalArgumentException(noMethodReport(methodName,target));

所以答案是 clojure 编译器依赖反射来为方法找到匹配的签名,当没有找到时它会引发它自己的异常。

在这种情况下,IllegalArgumentException 被正确引发,但 noMethodReport 错误消息会显示,这会导致混淆。

【讨论】:

    【解决方案2】:

    Character/isWhitespace 是一个 Java 方法(java.lang.Character 类的静态方法)。有关调用 Java 方法的语法示例,请参阅 Java interop

    常规的 Clojure 函数仅由它们的名称定义,而 Java 方法由它们的 signature 定义,其中包括它们的名称以及它们的参数的数量和类型。

    Character 类中定义的isWhitespace 方法的唯一变体是isWhitespace(char ch)isWhitespace(int codepoint)。因此,使用字符串调用isWhitespace 存在“无匹配方法”。

    【讨论】:

    • 这并没有解释为什么消息是“找不到方法”而不是“错误的参数”
    • @GuillermoWinkler:是的,我解释了问题的原因,但忘记明确回答问题。我现在添加了一个段落来解决这个问题。
    【解决方案3】:

    因为“”是类型字符串,而不是字符。

        user=> (type \a)
        java.lang.Character
        user=> (type "")
        java.lang.String
    

    字符串(java)类没有“isWhitespace”方法,而“字符”类有。你不能用螺丝刀钉钉子。

    那里有一些提示:

    How to represent empty char in Java Character class

    “”是一个空字符串。但是没有“空”字符的表示。虽然字符“\0”确实:

          user=> (Character/isWhitespace \0)
          false
    

    还有 isWhitespace 字符的文档:

    http://docs.oracle.com/javase/6/docs/api/java/lang/Character.html#isWhitespace%28char%29

    说:

    “从 U+0000 到 U+FFFF 的字符集有时称为基本多语言平面 (BMP)。码位大于 U+FFFF 的字符称为补充字符。Java 2 平台使用字符数组以及 String 和 StringBuffer 类中的 UTF-16 表示。在这种表示中,补充字符表示为一对 char 值,第一个来自高代理范围 (\uD800-\uDBFF),第二个来自低代理范围 (\uDC00-\uDFFF)。”空报价不属于哪个。

    换句话说,isWhitespace 方法无法“理解”一个空字符串。

    此外,来自clojure java interlop doc http://clojure.org/java_interop

    上面给出了访问字段或方法成员的首选惯用形式。实例成员表单适用于字段和方法。 它们都在宏扩展时扩展为对点运算符的调用(如下所述)。

    所以打字

          (Character/isWhitespace "")
    

    扩展到

           (. Classname instanceMember instance args*) 
    
           (. Character isWhitespace "")
    

    导致错误。

           user=>  (. Character isWhitespace "")
           java.lang.IllegalArgumentException: No matching method found: isWhitespace (NO_SOURCE_FILE:0)
    

    虽然您必须查看 clojure 源代码才能确认,但我的猜测是来自可能在下面进行的动态 java 编译:

      public class toto {
    
         public toto ()
         {
            Character.isWhitespace("");
         }
    
      }
    

    然后:

      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
    

    【讨论】:

    • 这就像在说“我告诉你搅拌机不存在的原因是因为你想搅拌砖。”正确的错误是“你不能混合砖块。”
    • 你建议的宏展开是错误的:(macroexpand '(Character/isWhitespace String)) => (.Character isWhitespace String)
    • 无论如何我认为你的回答没有回答这个问题,问题是为什么点宏不会因为无效的参数而失败,而是因为无效的方法。
    • “你不能用螺丝刀钉钉子。”正确的。但我的困惑是,如果我说(Screwdrive nail),我得到的错误不是“螺丝刀功能不接受钉子”,而是“螺丝刀不存在”。我知道它存在,因为我已经成功完成了(Screwdrive screw)
    • @Nathan Clojure 是一种基于 OO 语言的函数式编程虫胶。错误消息是您仍然可以看到 OO 的地方之一。 scerw.Screwdrive() 可能存在,但 nail.Screwdrive() 不存在因此错误消息。
    【解决方案4】:

    Character api 中没有像isWhitespace 这样的方法,它接受字符串作为参数。 isWhitespace 方法只接受 intchar 作为参数。

    【讨论】:

    • 当我说Character/isWhitespace时,我是不是没有准确地告诉它我想要哪种方法?我是不是要求它查找一个可以接受这种类型参数的方法?
    • @NathanLong,是的,Clojure 将根据传递的参数推断出正确的方法。
    • @NathanLong,这里重要的上下文是Java方法可以是overloaded
    • @jbm - 谢谢,这很有帮助!我不知道我正在调用 Java 方法,也不知道这些方法可能会被重载。 :)
    • @NathanLong:是的,这就是他的核心。与常规的 Clojure 函数不同,Java 方法不是单独识别它们的名称,而是通过它们的完整签名来识别,该签名由方法的名称和数量以及参数类型组成。
    猜你喜欢
    • 2011-11-09
    • 1970-01-01
    • 2022-10-17
    • 1970-01-01
    • 1970-01-01
    • 2016-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多