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])`在多个方面看起来更好。如果有某种用例让人们想要创建迭代器,我对那个问题有兴趣,但在这里我没有看到它。

by
这只是一个创建和消费迭代器的简单示例,而不是一个实际有用的示例。我原始帖子中提到的项目需要这个功能。我将询问库的作者澄清他们的用例。
by
上述两个链接的代码示例都没有展示迭代器的使用 - 它们都关于构建迭代器函数,而这些函数在项目中也从未被调用过。谁使用这些?为什么?
by
我最需要的迭代器的场景是创建一个自定义的映射类型。

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

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

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

这就是为什么我主要需要`iter`函数,这个函数仅在`RT`中可用,因为存在底层结构,我在重用其迭代器。

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

```
(defn iterator
  "从集合中获取迭代器的CLJC实用程序。"newLine
  [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`,以避免这些低覆盖率的实现(如我发送的那个)。你怎么看?
不,我认为这不是一个好主意。我认为把`iter`添加到CLJS中也不是一个好主意:)

你给我带来了一个答案。给我更多的问题。
> 这些都是关于构建迭代器函数的讨论,但项目中的这些函数从未被调用过。

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

你承认这些情况存在,这也是为什么一些Clojure内部功能“没有得到锁存”的原因。你承认威尔克提供的`iter`在JVM上的实现是“非常好的”。然而,你不认为它应该成为公共API的一部分或者是一个好主意,但你没有明确说明为什么有这种想法。

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

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

> 一般而言,迭代器很不Clojure。

这是一种排他性的、本质主义的态度。就象,男人的观点。:^)

Clojure在核心库中提供了许多使用“状态性的且通常不兼容并发的”功能的机会,正如你所指出的,它会故意使这些功能可访问。所以,从我站的角度来看,无论是“Clojure的特性”包括哪些,似乎都包括那些“不那么Clojure的”功能。

现在,让我问问,一个函数在“应该被考虑”之前必须满足什么任意条件?
退一步说,决定什么是核心API/语言的问题,是收集人们遇到的问题,筛选这些问题以找到模式,并基于频率、严重程度、解决方案的难度等因素来决定哪些问题是最重要的。

向这个过程提供输入的最好方法是尽可能清楚地描述你遇到的问题。当你遇到Meander中的这个问题时,为什么你想要一个迭代器?手头上的实际问题是什么?你考虑了哪些其他事物?为什么它们不够充分?

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

我的一般偏见是反对迭代器。迭代器在危险和命令式方面与seqs和reducer不同,而这并不是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`已经在那里,并且它所做的一切都符合我的需求,所以我并没有继续探讨。

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

因为要么是它们有复杂到我作为维护者不想卷入的复杂性,要么是因为它们导致语义的实现不正确。
by
> 我认为 CLJS 有 `iter` 并不是一个好主意。

这是问题的关键。CLJS 一直被当作 Clojure 的一种方言来对待,导致许多情况类似于这种情况。虽然我理解存在一些反对正式指定语言部分的观点,但至少对原始定义和默认名称空间中可解析的符号进行一定程度的定义,可以为未来的实现缓解这些问题。就我个人而言,我认为宏 `ns` 是问题的一部分,因为它很普遍(`in-ns` 更加原始但很少使用;CLJS 需要它),并且它指向核心,其中的内容是基于实现者认为应该包含的内容来任意决定的;类似这样的问题是自然的结果。

如果未来的语言设计包括对原始定义的最小化,以便可以轻松地实现平台特定符号的清晰分离,CLJ 和 CLJS 或其他实现之间的这些差异问题可能会消失。

顺便说一下,我是在有经验的情况下发表意见的。在过去的5年里,我 部分 实现了一个用于 Ruby 2.X.X 的 Clojure 解释器/编译器,一个 Clojure 的小步骤符号解释器,以及一个 Clojure 的部分评估器。在每种情况下,我都因缺乏清晰的语法和语义定义而无法完成项目。例如,有像 `case*` 这样的特殊形式,它们没有经过文档说明,需要检查编译器来了解它们的语义。

无论如何,我在这里没有提出任何要求。我只想分享我的观察。我认为 Clojure 有可能解决这样的问题。作为一名将近十年以专业和业余方式编写 Clojure 代码的人,我认为对语言的某些规范将铺平通往那个未来的道路。
by
我无法代表开发和维护 Clojure 的核心 Clojure 团队发言,但鉴于在过去十年中,一些人在各种公共论坛上多次提出关于 Clojure 规范的问题,我怀疑(a)Clojure 核心团队对编写此类规范没有兴趣,除了在实现中包含的文档字符串以及 clojure.org 上今天的许多页面官方文档(以及计划编写的那些)外,以及(b)如果这是真的,唯一可能编写的规范将由不在该团队中的某人编写,核心团队没有理由遵循它。
...