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,它通过应用transducers创建一个新的迭代器。最好也能在这方面有功能一致性。

by
需要知道 *为什么*?当前遇到了什么问题?
by
`transformer-iterator`更特别,可以避免使用。我之所以带来它,是因为我最终在我的CLJS实现中使用了它。但是仔细想想,在 my 案例中,`eduction`几乎可以起到相同的作用,所以我可以选择继续使用它。
by
eductions 封装迭代,可以在线程之间安全地传递,因此肯定更受欢迎。
0
by

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

迭代器在一般情况下很不像Clojure。它们是状态化的,并且通常不利于并发。当Clojure能够将其使用约束在其他调用(特别是transducer上下文)内时,它将依赖它们。

(iterator-seq (clojure.lang.RT/iter [1 2 3]))看起来很糟糕,而(seq [1 2 3])在很多方面看起来更好。如果存在一些使用场景使得人们想要创建迭代器,我对这个问题很感兴趣,但我在这里看不到。

by
这只是创建和使用迭代器的一个简单示例,并不是一个真正有用的示例。我在原始帖子中提到的项目需要这个函数。我会询问库的作者澄清他们的用例。
by
上述两个链接的代码片段都没有展示迭代器的使用 - 它们都关于构建迭代器函数,而且那些项目中的任何一个都没有调用这些函数。谁使用这些?为什么?
by
我最需要的迭代器用例是创建一个自定义的map类型。

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

Clojure处理keys的方式是首先在类型上触发`seq`,并且也像这里这样发送类型: https://github.com/clojure/clojure/blob/57f7c6ea164271d92cec5a12d22043c7029e3de2/src/jvm/clojure/lang/APersistentMap.java#L147-L154

这种实现需要类型实现`Iterable`接口,该接口必须返回一个`iterator`。

这就是为什么我非常需要 only Available in `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`是个好主意。 :)

您给我带来了一个答案。带来更多的问题。
> 这两个都涉及到构建迭代函数,但它们在那些项目中都没有被调用过

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

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

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

我同意。在核心库中,有许多函数并不被广泛使用也不被推广,但它们仍然存在,因为它们有其用途。换句话说,推广的可能性或使用范围不能成为排除`iter`的理由。

> 通常情况下,迭代器都很不像Clojure风格。

这是一种独断的、本质主义的立场。就像是,伙计,这是你的观点。 :^)

Clojure在核心库中包括了许多利用“有状态性和通常不适合并发”的东西的机会,正如你所指出的,故意让这些东西容易访问。所以在我这个位置看来,“Clojure风格”所包含的内容,似乎也包括这些“不像Clojure风格”的东西。

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

为这个过程提供最好的输入是尽可能清楚地描述你遇到的问题。当你遇到Meador中的这种情况时,为什么你想用迭代器?手中的实际问题是什么?你还考虑了哪些其他事情?为什么它们不够充分?

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

此外,在设计实现转换器的过程中,我们对此也进行了广泛讨论。转换器在实现上大量借鉴了迭代器,但为了安全起见,将其使用进行了封装。

尽管如此,我认为这并不是不切实际的想法,但根据我目前的判断,这种需求似乎不足以克服这一偏见。如果您想让我改变主意,请带给我问题。
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 或其他实现之间的差异问题很可能就会消失。

顺便说一句,我说话是基于个人经验的。在过去五年里,我为 Ruby 2.X.X 部分实现了 Clojure 解释器/编译器,为 Clojure 实现了一个小型符号解释器,并为 Clojure 实现了一个部分求值器。在每一种情况下,由于缺乏对语言语法和语义的明确定义,我从未完成过这些项目。例如,像 `case*` 这样的特殊形式没有文档说明,需要检查编译器以确定它们的语义。

无论如何,我这里没有提出任何要求。我只想分享我的观察。我认为 Clojure 有一个可能没有这种问题的未来。作为一个近十年以来在专业和个人层面都使用这个语言的程序员,我认为对语言的一些规范将铺平这条道路。
我就不能代表开发和维护 Clojure 的核心团队发言,但鉴于在过去十年中,在其他公共论坛上多次提出为 Clojure 编写规范的问题,我怀疑(a)Clojure 核心团队没有兴趣撰写这样的规范,除了在实现中包含的文档字符串和 clojure.org 上今天(以及仍计划撰写的)的许多页面的官方文档;(b)如果是这样,那么将会编写的唯一规范将是由不属于该团队的人编写的,Clojure 核心团队没有必要遵守它。
...