请在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

为了与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的观点,如果在语言的核心功能中有这样一个常见元素,那将是很好的,这将使其更具可移植性。

不是什么大问题,但在ClojureScript中也有transformer-iterator,它是通过在迭代器上应用转换器来创建一个新的迭代器。在这个特性上保持兼容性将很棒。

by
需要知道*为什么*?面临的问题是什么?
by
`transformer-iterator`比较特别,可以避免。我之所以提出它,是因为在CLJS实现中我最终使用了它。但再想想,eduction在我的情况下基本上也可以达到同样的效果,我可以继续使用它。
by
eductions包含迭代过程,并且可以为线程间交换提供安全的内容,因此绝对更受欢迎。
0
by

RT应该被认为是一个内部实现,不应直接调用。

迭代器在一般情况下非常不符合Clojure的风格。它们是具有状态的,通常不具有良好的并发性。Clojure在允许的情况下依靠它们在内部进行约束(尤其是转换器上下文中)。

相比`(iterator-seq (clojure.lang.RT/iter [1 2 3]))`,`(seq [1 2 3])`在多个方面看起来更好。如果有某些使用场景迫使人们创建迭代器,我对这个问题感兴趣,但在这里看不到它。

这只是一个创建和消费迭代器的简单示例,并不是一个真正有用的示例。我在原始帖子中链接的项目需要这个函数。我会要求库的作者解释他们的用例。
上面链接的两段代码都没有展示迭代器的使用 - 它们都关于构建迭代器函数,而这些函数从不在那些项目中调用。谁使用这些?为什么?
我需要迭代器的主要情况是创建一个自定义的映射类型。

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

Clojure 是通过首先在类型上触发 `seq` 操作来实现 `keys` 的,并且也发送了类型,如你所见: https://github.com/clojure/clojure/blob/57f7c6ea164271d92cec5a12d22043c7029e3de2/src/jvm/clojure/lang/APersistentMap.java#L147-L154

此实现需要类型实现 `Iterable` 接口,并且必须返回一个 `iterator`。

这是我在 `RT` 中需要 `iter` 函数的地方,因为在底层结构中我是重用其迭代器的。

使它跨平台的当前解决方案是

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

     :cljs
     (iter coll)))
```

所以尽管我真的不想使用迭代器,但这种情况下迫使我要使用。
在实现低级数据类型时,可能需要查询Clojure内部机制。实际上,这也是为什么它们可以通过公共方法使用而不是隐藏的原因。在这种情况下,我认为这里提供的代码对于 JVM 实现来说是完全合适的。我认为 `iterator` 不是一个应该广泛使用或推广的函数,也不适合添加到 Clojure API 中。
但是,鉴于 CLJS 已经有了 `iter` 函数,我们是否认为在这里使用它会有问题?我之所以知道第二种方法,是因为我幸运地在别人的代码中发现了它。

此外,该实现似乎没有RT中的`iter`功能覆盖得那么全面,它对处理映射和字符串有特殊的处理方式(正如CLJS中的`iter`所做的那样)。

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

这就是我认为Clojure也应该在其核心库中包含`iter`的主要原因,以避免这些覆盖范围较小的实现(就像我发送的那个)。您认为呢?
不,我不认为这是个好主意。我也不认为CLJS有`iter`是个好主意。:)

你给我带来了一个答案。带来更多的问题。
> 两者都是关于构建一个迭代函数,这些函数在上面的项目中都没有被调用过。

亚历克斯,我想澄清一点,这是不正确的。实际上,在一些情况下,Meander确实通过生成的宏代码使用`iter`函数,以使某些语义的实现更加容易(代码生成更简单)。尽管我后来发现了一个不使用`iter`来实现的机会,但在当时我没有这个解决方案,而`iter`解决了我的问题。

你承认这些情况存在,这也是一些Clojure内部函数没有“锁起来”的原因。你认可威尔克提供的`iter`的JVM实现“非常合适”。然而,你不认为它应该成为公共API的一部分,但这不是你的想法,也没有给出明确的原因。

> 我认为`iterator`不应该被广泛使用或推广

我同意。在核心中存在许多没有广泛使用也没有推广但仍然是为了便利而存在的函数。换句话说,推广的潜力或使用范围不能是排除`iter`的原因。

> 迭代器,从广义上来说,非常不符合Clojure风格。

这是一个排他性、本质主义的立场。就像,你的观点,伙计。:^)

Clojure包括许多在核心库中使用“有状态且不适合并发”的东西的机会,正如你所指出,故意使这些东西可访问。所以无论“clojurey”包括什么,从我所在的位置来看,这似乎包括那些“不符合Clojure风格”的东西。

现在,让我问一下,一个函数在“应该”被认为是“之前必须满足什么任意标准?
退一步来说,决定什么进入核心API/语言是一个收集人们遇到的问题,筛选出所有这些问题,找出模式,并根据频率、严重程度、工作区域的程度等决定哪些问题是需要解决的最重要问题的过程。

为此过程提供最佳输入的方式是尽可能清楚地描述你遇到的问题。当你在Meander中遇到这个问题时,为什么你需要迭代器?实际的问题是实实在在的什么?你考虑了哪些其他事情?为什么它们不够?

威尔克描述需要使用支持集合自身的迭代器方法是有用的例子。向核心添加`iter`是一种可能的解决方案。另一种方法是像他一样编写自己的函数,使用现有的Java api。另一种可能是在Clojure内部或库中封装构建自定义集合类型的一般需求。我强调,这里需要的范围并不广泛(大多数人不会创建自定义集合类型),他通过使用可用的Clojure/Java API没有使用`iter`函数就解决了他的问题,我没有其他有相同问题的人的例子(但也许有)。

我的总体偏见是反对迭代器。迭代器是危险的,以命令式方式存在,而序列和可折叠的都不是这样的。这不是偶然,它是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的变量。在模式中每次出现内存变量时,“分散”或从FIFO中取出第一个值,并将变量的绑定更新为FIFO中的剩余元素。这个过程一直持续到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有一个可能的未来,在那里这样的问题不存在。作为一个近十年来以专业和爱好方式使用该语言的人,我认为对语言的某些规范将铺平通往那个未来的道路。
by
我不能代表开发并维护Clojure的核心Clojure团队发言,但鉴于过去十年中,在各种公开论坛上多次提出对Clojure规范的疑问,我怀疑(a)Clojure核心团队没有兴趣编写这样的规范,除了在实现中包含的文档字符串和clojure.org今天拥有的许多官方文档(以及计划编写的那些),(b)如果是这样,唯一可能写出的规范将由团队以外的人制定,并且Clojure核心团队没有理由遵循它。
...