2024 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅 关于 页面以获取有关此功能的一些更多信息。

+1
Clojure
已关闭

新方法值功能是否预期如此工作?

(let [dir (jio/file "/Users/colin")]
  (.listFiles dir ^FileFilter File/.isDirectory))

它产生错误:找到多个匹配的方法:listFiles。然而,我预计这应该使用 FileFilter 类型标签来区分。

方法值似乎实现了 IObj

(instance? IObj File/.isDirectory)
=> true

但是,元数据似乎没有应用,至少对于 :tag

(let [x ^FileFilter File/.isDirectory]
  (meta x))
=> nil
标记为已解决
此问题发生在哪个 Clojure 版本上?

3 个回答

+1

另一种调用此方法的方式是使用带参数标签的限定方法,告知编译器选择哪个重载

(^[FileFilter] File/.listFiles dir File/.isDirectory)

在这种情况下,看起来对 File/.listFiles 的调用是直接发出的(没有包裹在一个 IFn 子程序中)。而 File/.isDirectory 是被包裹在子程序中的,但多亏了 ^[FileFilter] 参数标签,编译器才知道如何将其转换。

by
是的,这就是我最终选择的方法。这是将实际接口从具象化接口转换为fn版本自动重构的解决方案。这个解决方案不是模糊不清的,但由于可能需要添加许多进口以及实际更改,因此这是一种更具有侵略性的更改。
by
目前(1.12.0-beta1),这将把File/.isDirectory方法包裹在一个子程序中,然后将该子程序转换为所需的FileFilter功能接口。

我们打算支持从File::isDirectory到FileFilter的直接转换,但我们仍在考虑有关如何以及何时实现这一点的细微细节(将在1.12之后发生)。
0
by

这是那些需要使用带类型提示的 let 的情况之一

user=> (require '[clojure.java.io :as io])
nil
user=> (import '(java.io File FileFilter))
java.io.FileFilter
user=> (let [dir (io/file ".")
             ^FileFilter f File/.isDirectory]
         (.listFiles dir f))
#object["[Ljava.io.File;" 0x37c5fc56 "[Ljava.io.File;@37c5fc56"]

尽管如此,鉴于这一点

user=> (instance? clojure.lang.IMeta File/.isDirectory)
true

有点惊讶,你的原始版本和这个版本似乎都没有正常工作

user=> (let [dir (io/file ".")
             f ^FileFilter File/.isDirectory]
         (.listFiles dir f))
Syntax error (IllegalArgumentException) compiling . at (REPL:3:10).
More than one matching method found: listFiles
user=>
by
我明白了,所以这只有在为局部绑定而不是方法值本身提供类型提示时才会工作。我很想了解这被认为是将要修复的bug,我已向Cursive添加了将具象化形式转换为fns的功能,如果这不会得到修复,我将需要考虑这种情况。
“局部绑定类型提示,而不是方法值本身”,这是故意的,不是错误。在可以发生FI转换的两个地方,实际上存在“赋值”,包括源表达式(“右侧”)和分配的目标(“左侧”)。

在Java方法调用中,源是参数类型(该表达式的“类型”可以从许多地方来 - 参数的类型提示、符号或表达式的类型流或直接从字面表达式)。编译器实际上“不知道”源表达式的类型从何而来,但重要的是它可以错误。

在let绑定中,源是绑定初始化表达式,它同样有一个来自某个地方的类型,也可能是错误的。目标类型只能来自绑定符号的类型提示。

通常,Clojure开发者对这两种类型提示基本不加区分,并且通常具有相同的效果,因为绑定(左侧)将采用表达式的报告类型(右侧)。但它们在编译器中的处理方式以及对于隐式转换来说非常不同!所以,在let中,如果你正在请求显式转换为FI类型,则需要特别指定目标类型提示。

在Java方法调用中,那里的类型提示很棘手,因为它实际上有双重角色 - 从一种意义上说,你在声明参数源表达式类型,而从另一种意义上说,你可能在使用它来选择重载选项并避免反射。在这种情况下,它将很重要地选择重载,从而充当目标类型。如果你想分离这两个角色,你可以使用param-tags来实现,它们只独立于源参数表达式讨论目标重载选择。
0

"^FileFilter File/.isDirectory" 是在读取时给符号附加元数据

"(meta ^FileFilter File/.isDirectory)" 是查看evaluate符号File/.isDirectory的结果上的元数据,而不是符号本身

"(instance? IObj File/.isDirectory)" 显示evaluate符号File/.isDirectory的结果是实现IOb的东西,而不是File/.isDirectory本身实现IOb(但实际上它实现了,因为它是一个符号)

这导致我尝试了这个方法——它有效

user=> (require '[clojure.java.io :as io])
null
user=> (import '(java.io File FileFilter))
java.io.FileFilter
user=> (let [dir (io/file ".")]
         (.listFiles dir (with-meta File/.isDirectory {:tag FileFilter})))
#object["[Ljava.io.File;" 0x512d4583 "[Ljava.io.File;@512d4583"]
user=>
所以,这个问题在于编译器中适配器的创建并没有将符号的元数据应用到适配器上?这在我看来应该是这样的,因为这个行为至少对我非常惊讶。
不是,因为类型提示元数据只被编译器使用,所以运行时标记值无法回到过去来指导编译器如何编译事物。我只是在指出,File/.isDirectory求值得到的实例?检查,以及对File/.isDirectory求值得到的对象调用meta,实际上并没有传达任何信息
Sean的例子实际上显示的是当你通过函数调用隐藏File/.isDirectory求值得到的对象的类型,强制将其作为一个反射调用时,它才会工作。在他的例子中,您可以用identity调用替换with-meta调用,它也会工作。

因此,这看起来像是一个bug,因为在反射过程中方法选择过程在编译时和方法运行时不同。
你也可以这样做 '(.listFiles dir ^java.io.FileFilter (identity File/.isDirectory))' 来规避在生成的方法函数上类型提示被忽略的问题
]
如果我这么做

(def ^String x "foo")

那么我的理解是,元数据在读取符号时也会应用于元符号。然而,当编译器编译def形式时,它会知道从符号到变量本身的元数据需要传递。

我还没有详细检查编译器代码中的新案例,但我对这个案例的心理模型是这样的

(.listFiles dir ^FileFilter File/.isDirectory)

的概念是,元数据在读时应用于符号,然后编译器会计算出必须生成代码来创建方法适配器,符号将评估到该适配器。我预计它将使用元数据来确定要创建哪个适配器(FileFilter或FilenameFilter),然后将元数据应用于该对象,就像编译器在编译代码创建变量时那样。然后它可以使用适配器对象的元数据来确定应该编译哪个变种.csqFiles。

我不确定的是,适配器是按调用位置创建的,还是共享的。如果是共享的,那么我想适配器永远不能有针对调用位置特定的元数据。
你遗漏了一个步骤。

File/.isDirectory被视为方法字面量,因此它被转换为一个调用该方法的IFn。

然后(.listFiles dir x)被视为功能接口适配,所以从IFn到功能接口的适配器被生成。

这里的bug实际上是在编译(.listFiles dir File/.isDirectory)时,你应该得到一个反射警告,而不是错误(因为运行时反射将从一个可能的选择中选取一个(listFiles),然后为 File/.isDirectory 生成 IFn 的适配器)

由于 File/.isDirectory 导致的包装方法功能非常通用,这也在添加元数据时成为问题,因为在使用 fn 对象的 with-meta 上是非常糟糕的(在它周围添加了包装器,该包装器通过 apply 方法调用原始方法,这很慢,并更改了函数的身份,这是与 with-meta 合同相悖的)。


如果你使用函数字面量来包装方法而不是依赖于 Clojure 来完成它

   (let [dir (io/file ".")]
    (.listFiles dir ^java.io.FileFilter #(.isDirectory %)))

此方法是有效的,所以编译器应该做类似的事情(这并不是关于将从符号复制到运行时对象中的元数据,就像 vars 一样,这个技巧之所以存在是因为复制的元数据成为后续形式编译时环境的一部分,这在这里是不可能的)。这可能仅仅是编译器把标签元数据复制出去,使其可以用于编译器构建的 Expr 树。
by
谢谢,这对澄清我对此事的思考非常有用。我认为对于我的使用案例(将具体化的版本自动重构为方法值版本),最好的做法是更新包围的交互调用(在这种情况下是 .listfiles)以使用带参数标签的新语法来消除歧义。
...