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]`在几个方面看起来更好。如果有某些用例导致人们想要创建迭代器,我对这个问题的解决方案感兴趣,但在这里我没有看到这样的用例。

这只是一个创建和消耗迭代器的简单示例,不是一个有用的示例。我在原帖中链接的项目需要这个函数。我将向库作者询问其用例。
上面两个链接的代码示例都没有展示迭代器的使用——它们都是关于创建迭代器函数的,其中没有一个在这些项目中调用过。谁使用这些?为什么?
我主要需要迭代器来创建一个自定义的映射类型。

具体的细节是实现该类型的自定义 `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)))
```

所以虽然我真的不想使用迭代器,但这个情况迫使我这么做。
by
在实现底层类型时,可能需要访问Clojure的内部机制(实际上这也是它们存在而不是被隐藏在某个私有方法中的原因)。在这种情况下,我认为您的代码对jvm实现来说是完全合适的。我认为`iterator`不是一个应该被广泛使用或推广的函数,或者不应该被添加到Clojure API中。
by
但是考虑到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`的原因,以避免这些低覆盖标准的实现(就像我发送的那样)。您怎么看?
by
不,我认为这不算是一个好主意。我认为CLJS有`iter`也不算是一个好主意:)

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

亚历克斯,我想明确指出这是不正确的。其实,Meander确实使用通过宏代码生成的`iter`函数,在某些情况下更容易实现一些语义(代码生成更简洁)。虽然后来我发现了一种不需要`iter`就能实现的方法,但当时没有那样的解决方案,而`iter`解决了我的问题。

您承认这些情况存在,这也是为什么一些Clojure内部功能尚未“被锁定”。您承认威尔克提供的`iter` JVM实现“非常完美”。然而,您认为它不应该成为公共API的一部分,也不是一个好主意,但您没有给出明确的原因来解释您的这些观点。

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

我同意。核心库中存在许多不完全被广泛使用或推广的函数,但它们仍然存在,有其用途。换句话说,推广的可行性和使用场景不应该是排除`iter`的理由。

> 总之,迭代器很不符合Clojure风格。

这是一个排他性的、绝对的主张。你的看法,伙计。:^)

Clojure在核心库中提供了许多使用“具有状态且通常不支持并发的”功能的机会,正如您所指出的,这些功能被故意使其可访问。所以,无论“Clojure风格”包括什么,从我的角度看,似乎也包括那些“不符合Clojure风格”的东西。

现在,让我问一下,一个函数必须在满足什么任意标准之前才能被认为“应该”加入?
退一步来说,决定什么包含在核心API/语言中是收集人们遇到的问题,筛选所有这些问题,寻找模式,并基于频率、严重性、工作顺序水平等决定哪些问题是需要解决的最重要问题。

为该过程提供信息的最有效方式是尽可能清楚地描述您遇到的问题。当在Meander中使用时,为什么您需要一个迭代器?实际问题是什么?您考虑了哪些其他方法?为什么它们不足以解决问题?

威尔克关于需要使用后端集合实现他的自定义收集类型的迭代器方法的描述是一个有用的例子。将`iter`添加到核心库是一种可能的解决方案。另一种方法是编写自己的函数,使用现有的Java API,就像他所做的那样。另一种可能是在Clojure内部或库中封装构建自定义收集类型的一般需求。我注意到这里的需要范围并不常见(大多数人不会创建自定义集合类型),他没有使用`iter`函数,而是使用可用的Clojure/Java API解决了他的问题,我也无法找到其他有同样问题的人的例子(但这可能存在)。

我的一般倾向是反对迭代器。迭代器在危险性上与seqs和reductible不同,这是 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为空,也就是所谓的“耗尽”。当内存变量出现在重复模式中(类似于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核心团队不会有理由遵循它。
...