请在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标签的有效方法,告诉编译器你想使用哪个重载版本

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

在这种情况下,似乎File/.listFiles调用是直接发出的(没有用IFn简触发器包装)。而File/.isDirectory调用被包装在简触发器中,但由于有^[FileFilter]参数标签,编译器知道如何转换它。

是的,我就采用了这种方法。这是将接口从实现形式转换到函数版本的自动化重构。这个解决方案没有歧义,但是可能需要添加众多导入以及实际变更的原因,它是一个更具侵入性的变化。
当前(1.12.0-beta1),这将把File/.isDirectory方法包装在简触发器中,然后将该简触发器转换为所需的FileFilter函数接口。

我们打算支持从File::isDirectory直接到FileFilter的更优转换,但我们仍在考虑如何以及何时执行这一点(这将在1.12之后发生)。
0 投票

在这些地方,你需要使用具有类型提示的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=>
我知道了,这只在你为局部绑定而不是方法值本身进行类型提示时才有效。我很想了解这是否被认为是一个将 要修复的错误,我已经为Cursive添加了将形式化形式的翻译为函数的功能,如果这个错误不 会被修复,我将不得不考虑这种情况。
by
编辑 by
关于“类型提示局部绑定,而不是方法值本身”,这是故意的,并不是错误。在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 评估结果的实例? 检查,以及对 File/.isDirectory 评估结果调用 meta 并没有传达任何信息
Sean 的例子实际展示的是,当你通过传递一个函数调用来隐藏 File/.isDirectory 方法返回的对象类型,强制进行反射调用时,它就可以工作了。在他的例子中,你可以用对 idenitity 的调用来替换对 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到函数接口的适配器。

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

File/.isDirectory导致一个包装方法调用的相当通用的函数,这也是添加元数据的问题,因为使用with-meta在fn对象上是可怕的(在它周围添加一个包装器,调用原始的apply方法,这将变得缓慢,并更改函数的身份,这与with-meta的合约相冲突)


如果用函数字面量包装方法而不是依赖Clojure来处理

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

这有效,因此编译器也应该做类似的事情(这并不是将元数据从符号复制到运行时对象,像vars一样,这个技巧存在是因为复制的元数据成为了随后的形式的编译时环境的一部分,这在这里是不可能的)。这可能只是一个编译器复制标签元数据以便在编译器构建的Expr树中可用的问题。
谢谢,这真的有助于澄清我对此事的思考。我认为对于我的用例(自动重构实例化的版本到方法值版本),最好的办法是更新封装的Interop调用(本例中为.listfiles)以使用新的带有param-tags的语法来进行歧义消除。
...