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,它可以通过对一个迭代器应用转换器来创建一个新的迭代器。在这方面也希望有功能上的对等性。

 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
这只是一个创建和消费迭代器的简单示例,并不是一个实际有用的示例。我在原文中提到的项目需要这个函数。我会向库的作者询问它们的使用案例。
by
上面链接的两个代码片段都没有展示迭代器的使用——它们都是关于构建迭代器函数,但这些函数从未在那些项目中调用过。谁使用这些?为什么?
by
我主要需要迭代器的场景是创建一个自定义的映射类型。

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

Clojure 是通过首先在类型上触发 `seq` 来实现 `keys` 的,同时也可以看到: https://github.com/clojure/clojure/blob/57f7c6ea164271d92cec5a12d22043c7029e3de2/src/jvm/clojure/lang/APersistentMap.java#L147-L154

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

这就是我主要需要 `iter` 函数(仅在 `RT` 中可用)的原因,因为它有一个底层的结构,我在重用它。

当前的跨平台解决方案是

```
(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
它们都是关于构建迭代函数的,但这些函数在这两个项目中都没有被调用过。

Alex,我想澄清一下,这是不正确的。实际上,Meander确实通过生成的宏代码使用`iter`函数在某些情况下更容易实现一些语义(更简单的代码生成)。虽然我现在意识到可以使用没有`iter`的方式来实现这个功能,但在当时我没有那种解决方案,而`iter`解决了我的问题。

你承认这些情况存在,这也是一些Clojure内部没有被“隐藏”的原因。你承认Wilker提供的`iter`的JVM实现是“完美的”。然而,你认为它不应该成为公共API的一部分,但这不是一个好主意,但你没有给出明确的原因为什么你有这样的想法。

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

我同意。核心中存在许多不常使用且没有推广的函数,但它们仍然被提供,因为它们的实用性。换句话说,推广的可能性或广泛的用途范围不能成为排除`iter`的原因。

迭代器在总体上是非常不符合Clojure风格的。

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

Clojure包含了许多在核心库中使用“有状态和通常不适合并发”的功能的机会,如你所言,这是故意这样设计的。所以从我坐的地方看,不管“clojurey”包含什么,似乎都包括那些“不符合Clojure风格”的东西。

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

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

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

我对迭代器有一般的偏见。迭代器很危险,具有命令式特性,而序列和可递归并不是这样,这不是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的实现中大量依赖迭代器,但对其使用进行封装以确保安全。

即使如此,我认为这并不是不可能的,但需求的程度似乎还不够高,无法克服我的这种偏见。如果你想改变我的看法,请带来问题。
以下所有内容都被括号括起来,“我已经推导出一个新的解决方案,该方案不依赖于`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或其他实现之间的这些问题可能会消失。

FWIW,我也是凭借经验发言的。在过去的5年里,我部分实现了Clojure的Ruby 2.X.X解释器/编译器,一个小型的Clojure符号解释器,以及Clojure的局部评估器。在每种情况下,由于缺乏关于语言在语法和语义上的明确定义,我从未能完成这些项目。例如,有些特殊的语法形式(如`case*`)没有得到说明,需要检查编译器才能确定其语义。

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