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 的使用情况,并排除了 cloves 本身

[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
eductions封装了迭代,并允许你在线程之间安全地传递数据,因此绝对应该优先使用。
0
by

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

迭代器在本质上很不符合Clojure风格。它是有状态的,并且通常不适用于并发。当它可以限制使用在某个其他调用中时(尤其是转换器上下文),Clojure会内部使用它们。

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

by
这只是创建和消耗一个迭代器的简单示例,并不是一个实际的示例。我在原始帖子里连接的项目需要这个函数。我会要求库的作者澄清他们的使用场景。
by
上面链接的两段代码都没有演示迭代器的使用——它们都是关于构建迭代器函数,其中任何一个在那些项目中都没有被调用过。谁使用这些?为什么?
by
我主要需要迭代器的情况是创建自定义的映射类型。

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

Clojure 维护键的策略是首先在类型上触发 `seq`,同时也发送类型,就像这里看到的: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 impl是完全可以的。我不认为`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的内务操作还没有“藏起来”的原因。你承认威尔克(Wilker)提供的`iter`的JVM实现“非常好”。但你并不认为它应该成为公共API的一部分或者是一个好主意,但你没有给出一个明确的原因说明你为什么这样想。

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

我同意。在clojure核心库中存在许多不常使用也未被推广但却是必需的函数。换句话说,推广的可行性和使用的广泛程度并不能成为排除`iter`的理由。

一般来说,迭代器非常不符合Clojure的风格。

这是一个排他性、本质主义的态度。就像,你的观点,兄弟。:^)

Clojure库包括了众多使用“有状态且通常不适用于并发”功能的机会,并且正如你所指出的,故意使这些功能可供使用。所以无论“clojurey”包括什么,从我的角度看起来,“非clojurey”的东西也在其中。

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

向该过程提供输入的最佳方式是尽可能清楚地描述你所遇到的问题。当你遇到梅安德时,为什么你需要迭代器?手上实际的问题是什么是?你考虑过其他什么?为什么它们不足以解决问题?

威尔克自己描述需要使用背靠库来实现他自己的集合类型的迭代方法是有用的例子。将`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

此外,在设计和实现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或其他实现的差异问题很可能消失。

FWIW,我也有相关的经验。在过去5年中,我为实现Clojure的解释器/编译器(为Ruby 2.X.X)、Clojure的小型符号解释器以及Clojure的部分求值器做出了一些努力。在每种情况下,由于缺少关于语法和语义的清晰语言定义,我都无法完成这些项目。例如,存在像`case*`这样的特殊形式,它没有记录,需要检查编译器来确定其语义。

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