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,它可以通过应用transducer来创建一个新的迭代器。如果在这个功能上也能与之前保持一致就好了。

为什么要做这个功能呢?面临的是什么问题?
`transformer-iterator`更加具体,可能可以避免。我引入它是因为我在CLJS实现中使用了它。但考虑一下,`eduction`在我的情况下几乎一样,我可以继续使用它。
eductions封装了迭代,可以在线程之间安全地传递,因此绝对更受欢迎。
0

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

迭代器一般来说不太像Clojure风格。它们具有状态,通常不兼容并发。当Clojure可以限制其在某些调用中的使用(特别是transducer上下文)时,它会依赖它们。

使用`(iterator-seq (clojure.lang.RT/iter [1 2 3]))`似乎不好,而使用`(seq [1 2 3])`在多个方面似乎更好。如果有某些用例使得人们想要使用迭代器,我对这个问题很感兴趣,但在这里我没有看到。













```
(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`那样覆盖到事物(RT中的`iter`在处理map和字符串时有一些特殊的处理情况,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内部函数“未被锁定”的原因。你承认Wilker提供的`iter`的JVM实现“相当不错”。但你认为它不应该成为公共API的一部分,但这并不是你这样想的明显理由。

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

我同意。Clojure核心中存在许多不广泛使用也不被推广的函数,但它们仍然存在,因为它们的实用性。换句话说,推广或使用范围的大小不能是排除`iter`的理由。

> 一般而言,迭代器非常不符合Clojure的风格。

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

Clojure在核心库中提供了许多使用“有状态且通常不友好的并发”事物的机会,正如你所指出的,这是 故意让这些事物可访问的理由。所以无论“Clojure风格”包括什么,从我坐的位置来看,这似乎包括那些“不符合Clojure风格”的事物。

现在,让我问问,一个函数在“应该”被视为之前必须满足哪些任意标准?
退一步来说,决定什么进入核心API/语言是一个收集人们遇到的问题,并在这些问题中寻找模式的过程,并根据频率、严重性、解决方案的工作量等因素确定哪些问题是最重要的。

向该过程提供最佳输入的最好方法是尽可能清楚地描述你的问题。你在Meander中遇到这个问题时,为什么想使用迭代器?手头上的实际问题是是什么?你还考虑了其他什么事情?为什么它们不足以解决问题?

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

我对迭代器有偏见。迭代器在许多方面都是危险的和命令式的,而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的变量。在模式中对记忆变量的每次出现都会从FIFO中“散射”或取出第一个值,并更新变量的绑定到FIFO中的剩余元素。这会一直持续到FIFO为空,被称为“耗尽”。当记忆变量出现在重复模式中(想想Kleene星号)时,重复模式会产生值,直到至少有一个记忆变量未被耗尽。当重复模式编译为Clojure代码时,会检查模式是否包含记忆变量。如果是这样,它会为每个记忆变量编译迭代器,每个迭代器都可以通过`hasNext`进行测试是否耗尽,当然也可以使用`next`散播值。尽管这不是一个理想的解决方案,但我想要的语义很容易以这种方式实现。

> 你还考虑了哪些其他解决方案?

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

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

尽管已经过去了一段时间,但我想我也考虑过使用数组,但`iter`已经存在,并且按照我想要的方式工作,所以我没有去接触它。

> 为什么它们还不够充分?

因为这些方法要么有复杂的性,作为维护者我不愿意纠缠其中,要么导致语义实现错误。
> 我认为 CLJS 有 `iter` 不是个好主意。

这是问题的关键所在。CLJS 一直被当作 Clojure 的方言来看待,导致了许多类似的问题。虽然我理解对语言的一些部分进行正式定义可能存在偏见,但至少对原始值的某些定义和对空命名空间中默认可解析的符号的某些定义可能会有助于缓解未来实现的这些问题。个人而言,我认为 `ns` 宏是问题的部分原因,因为它非常普遍(`in-ns` 更原始但很少使用;CLJS 需要它),并且它引用核心(其内容是基于实现认为应该在哪里);像这个问题这样的问题是自然的结果。

如果未来设计的语言包含了对原始值的最小定义,以便能够轻松地实现平台特定符号的清晰分离,那么 CLJ 和 CLJS 或其他实现之间的问题很可能就会消失。

顺便说一句,我也有这方面的经验。在过去 5 年中,我曾为 Ruby 2.X.X 部分实现了 Clojure 解释器/编译器,一个小的 Clojure 符号解释器步骤,以及一个 Clojure 的部分评估器。在每种情况下,由于缺乏清晰的语言定义(无论是语法还是语义),我从未能够完成项目。例如,有像 `case*` 这样的特殊形式,这些形式未文档化,需要检查编译器以确定其语义。

无论如何,我没有提出任何要求。我只希望分享我的观察。我认为 Clojure 有一个可能的未来,在那里这类问题不存在。作为一名近十年职业上和业余时间都在用这个语言编程的人,我认为对语言的一些规范将铺平通往这个未来的道路。
我不能代表开发和维护 Clojure 的核心 Clojure 团队发言,但鉴于其他人已经在过去十年中在各种公共论坛上多次提出 Clojure 规范的问题,我怀疑:(a) Clojure 核心团队对编写这样的规范没有兴趣,除了在实现中包含的文档字符串和 clojure.org 当前的许多官方文档(以及计划要撰写的那些),(b) 如果这是真的,唯一的规范将是那些不在该团队的人写的,而核心团队没有理由去遵循它。
...