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 可以通过 clojure.core 函数公开 RT/iter 方法?

我“掌握”了本地.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,它通过对一个迭代器应用转换器来创建一个新的迭代器。那么在这一点上也能提供相同的特性会很好。

by
为什么要有所为“为什么”?主要的问题是什么?
by
`transformer-iterator`更具体一些,可以避免使用。我之所以提及它,是因为最终我在我的CLJS实现中使用到了它。但考虑到这一点,对于我的情况来说,`eduction`几乎可以做到相同的事情,所以我可以继续使用它。
by
eduction对迭代进行了封装,并且提供了一个可以安全地在线程间传递的东西,因此绝对更受欢迎。
0
by

`RT`应该是内部实现,不应直接调用。

迭代器总体上非常“非Clojure”。它们是有状态的,通常也不适合并发。当Clojure能限制它们在某些其他调用的使用时(特别是转换器上下文),它就会依赖它们。

(iterator-seq (clojure.lang.RT/iter [1 2 3]))看起来并不好,而(seq [1 2 3])在许多方面看起来更好。如果有一些使用案例会让人们想要创建迭代器,我对那个问题感兴趣,但在这里我并没有看到。

by
> 这两者都是关于构建一个不会在这些项目中调用的迭代器函数

Alex,我想澄清这一点是错误的。Meander确实使用由生成宏代码实现的`iter`函数,在某些情况下可以更简单地实现一些语义(简化代码生成)。尽管现在我意识到有机会在不使用`iter`的情况下实现这一点,但在当时我没有这种解决方案,`iter`解决了我的问题。

您承认这些情况存在,这也是一些Clojure内部组件没有“锁起来”的原因。您承认Wilker提供的`iter`的JVM实现是“完全可行的”。然而,您认为它不应该成为公共API的一部分,或者不是一个好主意,但没有给出明确的原因来解释您的想法。

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

我同意。核心库中存在许多不太常用也不太推广但仍然有用的函数。换而言之,推广能力或使用范围不能成为排除`iter`的理由。

> 通常,迭代器在Clojure中是非常不常见的。

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

Clojure包含许多机会在核心库中使用“具有状态性和通常不适用于并行的”事物,并且正如你所指出的,故意使这些事物可用。因此,从我的位置来看,无论“Clojure化”包含什么,似乎也包括那些“不Clojure化”的事物。

现在,让我问你,一个函数在被视为“应该”之前必须满足什么任意标准?
by
先退一步,决定什么要进入核心api/语言是一个过程,就是收集人们遇到的问题,筛选所有这些问题,以找到模式,然后根据频率、严重性、解决方法的程度等因素,确定哪些问题是解决的最重要的问题。

向该过程提供最佳输入的方式是尽可能清楚地描述你的问题。当你在Meander中遇到这个问题时,为什么你想使用迭代器?当前面临的问题是什么?你考虑了其他哪些方法?为什么它们不够充分?

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

我的总体偏见是反对迭代器的。迭代器是非常危险的且命令式的,而序列和可归约的不是,这不是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中"散布"(dispersing)或第一个值,并更新变量的绑定到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规范的疑问,我怀疑(a)Clojure的核心团队对编写这样的规范没有兴趣,除了实现中包含的文档字符串以及clojure.org上今天的数百页官方文档(以及仍计划编写的那些文档),(b)如果是真的,唯一可能编写的规范将是某个不在该团队中的人编写的,而Clojure核心团队没有理由遵循它。
...