2024年 Clojure 调查问卷!中分享您的见解。

欢迎!请查看关于页面以了解更多关于如何使用本站的信息。

+1
Clojure
已关闭

这种情况下是否预期可以使用新的方法值功能?

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

这产生了错误:“找到了多个匹配的方法:listFiles”。然而,我以为这应该使用文件过滤器类型标签来消除歧义。

方法值似乎实现了IObj接口

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

然而,似乎没有应用元数据,至少对于:tag来说是这样

(let [x ^FileFilter File/.isDirectory]
  (meta x))
=> nil
已关闭,备注:在 Clojure 1.12.0-beta2 中修复
这个问题发生在哪个 Clojure 版本?

3 个回答

+1

调用此功能还有另一种方法,即使用带有参数标记的合格方法告诉编译器你想要哪种重载

(^[文件过滤器] 文件/.listFiles 目录 文件/.isDirectory)

在这种情况下,似乎 文件/.listFiles 调用是直接发出的(没有用 IFn 悬挂函数包装)。文件/.isDirectory 被悬挂函数包装了,但多亏了 ^[文件过滤器] 参数标记,编译器知道如何转换它。

是的,这就是我最终采取的方法。这是从具体化的界面到 fn 版本的自动重构。该方案不会产生歧义,但由于可能需要添加多个导入以及实际的更改,因此这是一个更具有侵入性的改变。
目前(1.12.0-beta1),这将把文件/.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=>
明白了,这仅在给局部绑定传了类型提示时才生效,而不是对方法自己进行类型提示。我想知道如果这被认为是将会修复的bug,我在Cursive中添加了将实体形式翻译为函数的功能,如果这不会修复,我将不得不考虑这个情况。

编辑
关于“类型提示局部绑定而不是方法值本身”,这是故意的,不是bug。在可以发生FI转换的两个地方,实际上是进行了“赋值”,既有源表达式(“右侧”)和被分配的目标(“左侧”)。

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

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

通常,Clojure开发者对此不加以区别地使用类型提示,因为它们通常具有相同的效果,因为绑定(LHS)将采用表达式(RHS)的报告类型。但这些问题在编译器中的处理方式非常不同,对于隐式转换,这非常重要。所以,在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计算出的元数据实际上并没有传达任何信息
Sean的例子实际上展示的是,当你通过将该对象传递给一个函数调用来隐藏File/.isDirectory评估到的对象的类型,从而强制执行反射调用时,它是有效的。在他的例子中,你可以用identity调用替换with-meta调用,它也会有效。

所以这看起来像一个bug,方法选择过程在编译时反射和运行时反射时不同。
by by
你还可以执行类似'(.listFiles dir ^java.io.FileFilter (identity File/.isDirectory))'的操作来绕过生成的方法函数类型提示被忽略的事实
的情况。
comment(ByVal Fleming1) by
如果我用

(def ^String x "foo")

那么我的理解是,在读取时金属也应用于符号x。然而,当编译器编译def形式时,它会知道要把金属从符号转移到var本身。

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

(.listFiles dir ^FileFilter File/.isDirectory)

金属在读取时应用于符号,然后编译器确定它必须生成代码来创建一个会评估为符号的方法适配器。我预计它会使用金属来决定要创建哪种适配器(FileFilter或FilenameFilter),然后将其应用于该对象,就像编译器在编译创建var的代码时做的那样。然后,它可以使用适配器对象上的金属来确定应该编译哪个变体的.listFiles。.

我不确定适配器是针对调用点创建的,还是共享的。如果是共享的,那么我猜适配器永远不会将调用点特定的金属应用于它们。
) by
你遗漏了一步。

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

那么 (.listFiles dir x) 被视为功能接口适配,因此从 IFn 到功能接口的适配器被生成。

这里的错误实际上是在编译 (.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树中使用。
by
谢谢,这对澄清我关于这个问题的思考非常有益。我认为对于我的用例(自动将具体化版本更新到方法值版本),最好的办法是更新外围的 interop 调用(本例中的 .listfiles)以使用具有参数标签的新语法来消除歧义。
by
...