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] 基于参数标签,编译器知道如何转换它。

是的,我最后采用了这种方式。这是从具体化接口到函数版本的自动重构。该解决方案不是模糊的,但由于可能需要添加许多导入以及实际更改,所以它是一种更侵入性的改变。
当前(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 添加了将具体化形式转换为 fn 的功能,如果这个错误不会修复,我将不得不考虑这个情况。

编辑
关于“在局部绑定中使用类型提示,而不是方法值本身”,这是故意的,而不是错误。在FI转换可能发生的两个地方,实际上有“赋值”与一个源表达式(“右侧”)和一个分配的靶标(“左侧”)。

在Java方法调用中,源是参数类型(该表达式的“类型”可以来自许多地方——参数的类型提示、符号或表达式的类型流,或直接来自字面表达式)。编译器实际上并不“知道”源表达式类型来自哪里,但重要的是它可能会出错。在这个情况下,目标是您调用的Java方法的参数——这是已知的,或者是反射的。

在let绑定中,源是绑定初始化表达式,这也有一个从...某处来的类型,也可能出错。目标类型只能来自绑定符号的类型提示。

通常,Clojure开发人员对这两种类型提示不太区分,它们通常具有相同的效果,因为绑定(LHS)将承担表达式的报告类型(RHS)。但在编译器中处理方式和隐式转换有很大不同!因此,在let中,如果您请求到FI类型的显式转换,那么具体地类型提示目标是重要的。

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

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

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

"(instance? IObj File/.isDirectory)"表明evaluating symbol 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所评估的对象,并在评估结果上调用meta实际上没有传达任何信息
Sean的例子实际上是显示,当你通过通过函数调用遮蔽 File/.isDirectory 评估到的对象的类型强制进行反射调用时,它会起作用。在他的例子中,你可以用 identity 调用替换 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 被视为方法字面量,因此被转换为一个调用该方法的 I Fn。

然后 (.listFiles dir x) 被视为功能接口适配,因此从 I Fn 到功能接口的适配器被生成。

这里的真正错误是当编译 (.listFiles dir File/.isDirectory) 时,应该得到一个反射警告而不是错误(你最终会得到错误,因为运行时反射将选择可能的 listFiles 之一,并将其适配到 File/.isDirectory)

事实是,File/.isDirectory导致一个相当通用的函数包装了方法调用,这也会在添加元数据时成为问题,因为在对fn对象使用with-meta时是非常糟糕的(会添加一个包装器,该包装器使用apply调用原始函数,这很慢,并且改变了函数的身份,这与with-meta的契约相冲突)。


如果你使用函数字面量来包装方法而不是依赖Clojure来完成它,那么就是这种情况。

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

这是有效的,所以编译器应该做类似的事情(这并不是说从符号到运行时对象的元数据复制,比如对于vars,这种技巧存在是因为复制的元数据成为了后续形式编译时环境的一部分,这是不可能的)。这可能只是编译器复制标签元数据以使其在编译器构建的Expr树中可用的一个简单问题。
by
谢谢,这对阐明我对此事的思考真的很有帮助。我认为对于我的用例(将具象版本自动重构为方法值版本),最好的做法是更新封装的interop调用(在这种情况下是.listfiles)以使用带有param-tags的新语法来消除歧义。
...