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

另外一种调用此方法的方式是使用带有关联的参数标记的合格方法,以通知编译器您需要哪个重载

(^[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方法调用中,那里的类型提示很棘手,因为它实际上具有双重角色 - 在一个意义上,你正在声明参数表达式类型,而在另一个意义上,你可能正在使用它来选择重载并避免反射。在这种情况下,它将重要地选择重载,因此充当目标类型。如果你想分离这两个角色,你可以使用param-tags,它只独立地从源参数表达式讨论目标和重载选择。这将带你回到glchapman给出的答案。
0
by

"^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返回的对象类型,强制进行反射调用时,它会正常工作。在他给出的例子中,你可以用对identity的调用替换with-meta的调用,也会正常工作。

因此这个问题似乎是一个bug,其中反射在编译时和方法选择过程与运行时有所不同。
by
你还可以这样做:"(.listFiles dir ^java.io.FileFilter (identity File/.isDirectory))"。这旨在解决生成的方法函数上的类型提示被忽略的事实。
'
by
如果我这样做:

(def ^String x "foo")

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

我尚未仔细研究编译器代码中的新情况,但这个情况在我的心中是这样的

(.listFiles dir ^FileFilter File/.isDirectory)

元数据在读取时应用到符号上,然后编译器决定要为该符号生成一些代码来创建一个方法适配器。我预期它会使用元数据来确定要创建的适配器(FileFilter或FilenameFilter),并将元数据应用到那个对象上,就像编译器在编译代码创建变量时做的那样。然后,它可以使用适配器对象上的元数据来确定应该编译哪种.listFiles变体。

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

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

然后 (.listFiles dir x) 被视为一个函数接口适配器,因此会生成一个从 IFn 到函数接口的适配器。

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

File/.isDirectory 导致的相当通用的函数包装方法调用,这也为添加元数据带来了问题,因为使用 fn 对象的 with-meta 很糟糕(围绕原函数添加了一个调用原函数的包装器,这很慢,并且改变了函数的身份,违反了 with-meta 的契约)。


这是一个事实:如果你用函数字面量来包装方法而不是依赖于 Clojure 来做

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

这可行,所以编译器应该做类似的事情(这不仅是将符号的元数据复制到运行时对象,就像 vars 一样,这种技巧之所以存在,是因为复制的元数据成为后续表达式的编译时环境的一部分,这是不可能的)。可能只是编译器将标签元数据复制出来以便在编译器构建的 表达式树 中可用。
by
感谢,这对我理解这个问题非常有用。我认为对于我的用例(将具体化的版本自动重构为方法值版本),最好的办法是更新封装的互操作调用(在这种情况下是 .listfiles),使用带参数标签的新语法来消除歧义。
by
...