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 1.12.0-beta2 中修复
这是在哪个版本的 Clojure 中发生的?

3 答案

+1 投票

另一种调用此的方式是使用带有 param-tag 的合格方法来告诉编译器你想要哪个重载

(^[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
我明白了,这只有在你对本地绑定进行类型提示时才会工作。我很感兴趣,如果这被视为一个将被修复的缺陷,我已经向 Cursive 添加了将具体化形式转换为 fn 的功能,如果这不是要修复的,我必须考虑这种情况。

编辑
关于 "对局部绑定进行类型提示,而不是方法值本身",这是故意的,不是一个错误。在两个可能进行FI转换的地方,实际上存在带有源表达式("右侧")和分配目标("左侧")的"赋值"。

在Java方法调用中,源是参数类型(该表达式的"类型"可以来自很多地方 - 参数的类型提示、符号或表达式的类型流,或直接来自字面表达式)。编译器实际上"不知道"源表达式的类型从何而来,但重要的是它可能是错误的。在这种情况下,目标是您正在调用的Java方法的参数 - 这要么是已知的,要么是反射的。

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

通常,Clojure开发者对这两者进行类型提示时大多数情况下没有区别,它们通常具有相同的效果,因为绑定(左侧)将采用表达式的报告类型(右侧)。但它们在编译器中的处理和隐式转换上非常不同!所以,在let中,如果您要求显式转换到FI类型,明确类型提示目标是很重要的。

在Java方法调用中,类型提示在那里是复杂的,因为它实际上具有双重角色 - 从一个意义上讲,您正在声明参数的源表达式类型,从另一个意义上讲,您可能在使用它进行Java调用以选择重载选项并避免反射。在这种情况下,它将很重要选择重载并因此作为目标类型。如果您想分离这两个角色,您可以使用param-tags来实现,它只独立于源参数表达式讨论目标和中载选择。这将带您回到glchapman的答案。
0

"^FileFilter File/.isDirectory" 在读取时将元数据附加到符号上

"(meta ^FileFilter File/.isDirectory)" 正在查看对符号File/.isDirectory求值后的结果的元数据,而不是符号本身

“(instance? IObj File/.isDirectory)”显示,评估File/.isDirectory符号的结果是实现了IObj接口的某物,而不是File/.isDirectory本身实现了IObj接口(但实际上它是,因为它是符号)

这导致我尝试这样做——这是有效的

user=> (require '[clojure.java.io :as io])
nil
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求值的instance?检查,以及对File/.isDirectory求值使用meta的操作实际上没有传递任何信息
Sean的例子实际上显示的是,通过通过函数调用来隐藏File/.isDirectory求值对象的类型,强制执行反射调用时它就会工作。在他的例子中,你可以用身份调用代替with-meta调用,它也会工作。

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

(def ^String x "foo")

那么我的理解是,元数据在读取时间也被应用到符号x上。然而,当编译器然后编译def形式时,它知道要从符号本身传输元数据。

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

(.listFiles dir ^FileFilter File/.isDirectory)

元数据在读取时间应用到符号上,然后编译器确定它必须生成代码来创建一个符号将会计算的方法适配器。我期望它会使用元数据来确定要创建哪个适配器(FileFilter或FilenameFilter),然后像编译器在编译创建变量的代码时那样,将元数据应用到该对象上。然后它可以使用适配器对象上的元数据来确定应该编译哪个.ListFiles的变体。

我不确定的是,适配器是按调用点创建,还是共享的。如果它们是共享的,那么我猜适配器永远不会应用调用点特定的元数据。
你遗漏了一步。

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

然后 (.listFiles dir x) 被视为功能性界面适配,因此会生成IFn到功能接口的适配器。

这里的错误实际上是在编译时发生的。(.listFiles dir File/.isDirectory),你应该得到一个反射警告而不是错误(因为你将来会得到错误,因为运行时的反射会选择可能的列表之一,然后为该列表适配 File/.isDirectory 的 IFn)。

File/.isDirectory 导致一个相当通用的函数封装方法调用也是一个问题,因为它为添加元数据而烦恼,因为使用 with-meta 的 fn 对象是可怕的(在它周围添加一个包装器,该包装器调用原始的 apply,这很慢,并且更改了函数的身份,这与 with-meta 的契约相悖)。


使用函数字面量包装方法而不是依赖于 clojure 来完成它是这种情况。

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

这可行,所以编译器应该做类似的事情(这不涉及从符号复制元数据到运行时对象,就像 vars 一样,这个技巧之所以存在,是因为复制的元数据变成了随后的形式的部分,这是不可能在这里发生的)。可能只是一个编译器复制标签元数据让它可用于编译器构建的表达式树的问题。
by
感谢,这对我明确思考这件事非常有用。我认为对于我的用例(自动将具象版本更新为方法值版本),最好的办法是将封装调用更新为使用新的带有参数标签的语法来明确提出歧义。
by
...