2024 年 Clojure 状态调查! 中分享您的想法。

欢迎!请查阅 关于 页面以获取有关该系统更多信息。

+3
Clojure
编辑

我见过几次使用 clojure.lang.RT/iter 来检索 Clojure 集合迭代器的代码。

(iterator-seq (clojure.lang.RT/iter [1 2 3]))

例如:

https://github.com/noprompt/meander/commit/d2310daaa485afb4e15ceda72aa57f97ea90f284

https://github.com/wilkerlucio/cljc-misc/blob/bb3c8016cace18db5caa5fe0aa5df7a507935f8d/src/main/com/wsscode/misc/coll.cljc#L262

为了让 babashka 与 Clojure 保持兼容,我想知道是否应该公开 clojure.lang.RT。我并不是完全确信它是应该公开的,因为 clojure.lang.RT 可能是 Clojure 的实现细节。

相反,也许 Clojure 可以将 RT/iter 方法作为一个 clojure.core 函数暴露出来?

我对我本地 .m2 中的 clojure.lang.RT 使用进行了梳理,排除了 clojure 本身。

[clojure.lang.RT/loadClassForName 15]
[clojure.lang.RT/iter 5]
[clojure.lang.RT/loadLibrary 4]
[clojure.lang.RT/assoc 4]
[clojure.lang.RT/classForName 3]
[clojure.lang.RT/load 2]

请注意,ClojureScript 有一个 iter 函数。

$ plk
ClojureScript 1.10.597
cljs.user=> (iter [1 2 3])
#object[cljs.core.RangedIterator]
cljs.user=>

$ clj
Clojure 1.10.1
user=> (iter [1 2 3])
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: iter in this context

2 答案

0

我同意 @borkdude 的看法,如果能在该语言中对这个常用项有一个核心函数那就太好了,这会让它更易于移植。

这不是什么大事,但在CLJS中也有transformer-iterator,它通过对一个迭代器应用转换器来创建一个新的迭代器。如果在这方面也能实现功能一致性将会很棒。

为什么想要这个功能?当前遇到的问题是什么?
`transformer-iterator`更具有特定性,可以避免使用。我引入它是由于我在CLJS实现中用了它。但考虑到这一点,`eduction`在我的情况下几乎一样工作得很好,我可以继续使用它。
eductions封装了迭代过程,并且可以在线程之间安全地传递,所以绝对更受欢迎。
0

RT应被视为内部实现,不应直接调用。

迭代器一般不太符合Clojure的风格。它们是具有状态的,通常不利于并发。Clojure会在可以限制它们使用的其他调用(特别是转换器上下文)中使用它们。

`(iterator-seq (clojure.lang.RT/iter [1 2 3]))`看起来不好,而`(seq [1 2 3])`在多个方面看起来更好。如果有某种用法驱使人们想要创建迭代器,我对这个问题很感兴趣,但我没有在这里看到这种情况。

by
这只是一个创建和消费迭代器的简单示例,而不是一个实际有用的示例。我在原文中提到的项目需要这个函数。我会询问库的作者明确其用法。
by
上面的两个链接代码片段都没有展示迭代器的使用——它们都是关于构建迭代器函数,但这些项目都不会调用这些函数。谁使用这些?为什么?
by
我需要迭代器的主要情况是创建一个自定义的 map 类型。

具体细节是如何实现该类型的自定义 `keys`。

Clojure 实现键的方式是首先对类型触发 `seq` 操作,并像这样发送类型:https://github.com/clojure/clojure/blob/57f7c6ea164271d92cec5a12d22043c7029e3de2/src/jvm/clojure/lang/APersistentMap.java#L147-L154

这种实现要求类型还必须实现 `Iterable`,它必须返回一个 `iterator`。

这就是为什么我特别需要 `iter` 函数,它仅在 `RT` 中可用,因为底层的结构,我在重用其迭代器。

使它能够在不同平台上工作的当前解决方案是

```
(defn iterator
  "CLJC 工具,用于从集合中获取迭代器。"
  [coll]
  #?(:clj
     (if (instance? java.lang.Iterable coll)
       (.iterator ^java.lang.Iterable coll)
       (.iterator (seq coll)))

     :cljs
     (iter coll)))
```

所以尽管我并不真的想使用迭代器,但这种情况下我不得不使用。
by
在实现底层类型时,可能需要访问Clojure的内部实现(实际上这也是为什么它们可以被访问,而不是被锁定在一些私有方法中)。在这种情况下,我认为你这里的代码对jvm实现来说是完全合适的。我认为`iterator`不是应该被广泛使用或推广的函数,也不应该被添加到Clojure API中。
by
但是鉴于CLJS有`iter`,你会认为这种一致性很糟糕吗?我知道的第二种方式,完全是因为我在其他人的代码中幸运地发现了它。

此外,这种实现似乎没有`iter`从RT那样覆盖所有的内容,处理映射和字符串有特殊情况(就像CLJS中的`iter`一样)。

RT中的`iter`代码: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L571-L614

这就是为什么我认为Clojure应该在核心库中也有`iter`,以避免这些低覆盖率实现(就像我发送的那个一样)。你有什么看法?
by
不,我认为这不是一个好主意。我不认为CLJS有`iter`是好事 :)。

你给我带来了解答。给我带来更多问题吧。
by
> 它们都是关于构建迭代函数的,但这些函数在该项目中从未被调用

亚历克斯,我想澄清,这是不正确的。实际上,Meander会通过生成的宏代码使用`iter`函数,在一些情况下以更简单的方式实现一些语义(简化代码生成)。虽然我现在已经意识到可以在没有`iter`的情况下实现这一点,但在当时我没有这样的解决方案,而`iter`解决了我的问题。

您承认这些案例存在,这也是为什么一些Clojure内部尚未“锁住”的原因。您承认Wilker提供的`iter`的JVM实现“非常出色”。然而,您并不认为它应该是公共API的一部分或是一个好主意,但您没有清楚地说明为什么这些是您的看法。

> 我认为`iterator`不是一个应该广泛使用或推广的函数

我同意。在核心中存在许多仅限于特定用途的函数,它们既不广泛使用也不推广,但仍然存在,因为它们的实用性。换句话说,推广性或使用范围不能作为排除`iter`的理由。

> 就一般来说,迭代器非常不像Clojure风格。

这是一个排他性的、本质主义的立场。就像,你个人的看法,哥们。 :^)

Clojure包含了在核心库中使用“具有状态的并且通常不友好的并发性”的多种机会,而且在你的观点中,故意使这些功能可访问。所以无论“clojure风格”包括什么,从我坐的角度来看,它似乎包括那些“不像Clojure风格”的东西。

现在,我来问一下,一个函数在“应该”被考虑之前必须满足哪些任意的标准?
by
回顾一下,决定什么进入核心API/语言是收集人们遇到的问题,对这些问题进行筛选,找出模式,并根据频率、严重程度、工作水平等因素决定哪些问题是解决的最重要。

为此过程提供最佳输入的方式是尽可能清晰地描述你遇到的问题。当你遇到Meander中的问题时,为什么你想使用迭代器?手头上的实际问题是什么?你考虑过其他什么方法?为什么它们不足以解决问题?

Wilker描述了他的自定义集合类型需要使用后端集合来实现迭代器方法的必要性,这是一个有用的例子。向核心添加`iter`是可能的解决方案之一。另一个解决方案是像他那样使用现有的Java API编写自己的函数。另一个可能的解决方案是将构建自定义集合类型的通用需求封装在Clojure内部或库中等。我注意到,这里的需求范围不太常见(大多数人不会创建自定义集合类型),他没有使用`iter`函数就解决了他的问题,我没有其他有相同问题的人的例子(但也许他们确实存在)。

我的一般偏见是反对迭代器。迭代器在行为上既危险又命令式,而seqs和reducibles并不是,这并不是Clojure API中没有迭代器的巧合。这不仅是我的看法,也是Rich的看法,我在许多地方都传达了这一点。

* https://clojure.org/about/functional_programming#_extensible_abstractions
* https://clojure.org/reference/sequences
* https://clojure.org/news/2012/05/08/reducers
* https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/ClojureIntroForLispProgrammers.md

此外,在设计实施 transducers 的过程中,我们对此进行了广泛的讨论,transducers 在实现上依赖迭代器,但其使用经过封装以确保安全。

即便如此,我认为这个方案仍可行,但在我看来,其需求水平尚未足够高,以克服那种偏见。如果您想改变我的看法,请提供问题。
by
以下所有内容都放在了“我后来得到了一个新的解决方案,它不依赖于`iter`,但在我需要`iter`解决问题的时候,它做到了我需要它做到的事。”这句话中。

> 当你在 Meander 中遇到这个问题时,为什么想要一个迭代器?

严格来说,我最初并不需要迭代器,但最终它“解决了”我的问题,如下所述。

>  手头实际的问题是什么?

这个问题有几个交集。Meander 有一个模式替换的概念,你可以把它看作是对模式匹配的对应物。匹配将模式应用于对象,返回一组绑定,替换将绑定应用于模式,返回一个对象。特别是在重复模式中包含一种称为“记忆变量”的类型变量的替换模式的编译,这就是问题出现的地方。

记忆变量是与一个先入先出队列(fifo)相关联的变量。模式中的每个记忆变量的出现都会从队列中“分散”或输出第一个值,并更新变量的绑定到队列中剩余的元素。这将继续直到队列为空,并被说成是“耗尽”。当记忆变量出现在重复模式中(例如,Kleene 星号)时,重复模式将产生值,只要至少有一个记忆变量未被耗尽。当重复模式编译成 Clojure 代码时,会检查模式是否包含记忆变量。如果是这样,它将为每个记忆变量编译迭代器,每个迭代器都可以通过 `hasNext` 测试耗尽状态,当然,也可以通过 `next` 分散值。虽然这不是一个理想的解决方案,但我想要的语义以这种方式很容易实现。

> 你还考虑了哪些其他方式?

最初,我尝试了一个纯解决方案,但导致了大量复杂性和头痛。这更多与依赖于`iter`的糟糕设计有关,而与纯方法关系不大。

我还尝试了`volatile!`,但在某些情况下产生了错误的结果。

时间已经过去了很久,但我想我已经考虑过使用数组了,但`iter`已经存在,并做了我需要它做的事,所以我懒得去管它。

> 为什么这些方法不够?

因为要么存在复杂性,作为维护者我不愿意参与其中,要么因为它们导致了对语义的错误实现。
by
>> 我认为 CLJS 有 `iter` 并不是一个好主意。

这是问题的关键。CLJS 一直被当作 Clojure 的方言,导致许多情况如这个。虽然我理解对语言的某些部分进行正式指定存在一些偏见,但至少对于在空命名空间中默认可解析的原始值和符号进行一些最小定义,可能会缓解这些问题,为未来的实现提供一些帮助。我个人认为 `ns` 宏是问题的部分原因,因为它非常普遍(`in-ns` 是更原始的宏但很少使用;CLJS 要求它),并且它指向核心,核心的内容是基于实现者认为应该有什么内容的任意性;像这个问题这样的问题是一个自然的后果。

如果语言的未来设计包含对原始值的最低定义,使得能够轻松实现平台特定符号的清晰分离,那么 CLJ 与 CLJS 或其他实现之间的差异问题很可能会消失。

(仅供参考)我从经验出发。在过去 5 年里,我 * 部分地 * 实现了用于 Ruby 2.X.X 的 Clojure 解释器/编译器,Clojure 的小规模符号解释器,以及 Clojure 的部分评估器。在每种情况下,我从未能够完成项目,因为缺乏在语法和语义上的清晰语言定义。例如,有一些特殊形式,如 `case*`,没有文档记录,需要检查编译器来确定其语义。

不管怎样,我这里没有要求。我只想分享我的观察。我认为 Clojure 有一个可能没有这些问题未来的可能性。作为一名专业和作为爱好近十年写 Clojure 的人,我认为对语言进行一些规范将铺平通往那个未来的道路。
by
我不能代表开发 Cloujure 并维护它的核心团队发言,但鉴于过去十年中人们多次在各种公开论坛上提出 Clojure 规范的问题,我怀疑(a)Clojure 核心团队没有兴趣编写这样的规范,除了实现中包含的文档字符串和 clojure.org 上今天有大量官方文档,以及计划要编写的那些,和(b)如果这是真的,那么唯一可能被编写的规范将是由不在该团队中的人编写的,Cloujure 核心团队没有理由遵从它。
...