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

为了与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的风格。它们是状态的,并且通常不友好于并发。当可能约束其使用在某个其他调用(尤其是transducer上下文)内部时,Clojure会依赖它们。

(iterator-seq (clojure.lang.RT/iter [1 2 3]))看起来不合适,而(seq [1 2 3])在几个方面看起来更好。如果有某种使用情况会让人想去创建迭代器,我对这个问题很感兴趣,但在这里我看不到。

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

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

Clojure 实现keys的方式是首先在类型上触发 `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)))
```

所以我并不想使用迭代器,但这个情况迫使我这样做。
在实现低级类型时,可能需要访问Clojure的内部机制(实际上也正是如此,这正是为什么它们是可用的,而不是被锁在一些私有方法中)。在这种情况下,我认为你这里的代码对于jvm实现来说完全没问题。我不认为`iterator`是一个应当广泛使用或推广的功能,或者应该将其添加到Clojure API中。
by
但鉴于CLJS有`iter`,你认为这样做会不好吗?我之所以知道那种第二种方法,是因为我在别人的代码中幸运地找到了它。

此外,该实现似乎并不像RT中的`iter`那样覆盖所有的内容,那里有处理map和字符串的特殊情况(就像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在核心库中提供了许多使用“状态性和一般不便于并发”的东西的机会,并且正如你所指出的,正是为了故意使这些事物可访问。所以,不管“Clojure style”包括什么,从我的角度看,这似乎也包括那些“不符合Clojure风格”的东西。

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

向此过程提供最佳意见的方式是尽可能清楚地描述您遇到的问题。当你在Meander中遇到这个问题时,为什么你想使用迭代器?实际的问题是什么?你考虑过其他什么?为什么它们不够?

Wilker关于需要实现他自己的集合类型的迭代器方法,并使用后端集合作为示例,这是一个有用的例子。将`iter`添加到核心是其中一个可能的解决方案。另一个是像他一样使用现有的Java API编写自己的函数。另一个可能是将构建自定义集合类型的一般需求封装到Clojure内部或库中等等。我注意到的需求范围并不常见(大多数人不会创建自定义集合类型),他通过使用可用的Clojure/Java API来解决问题,而没有使用`iter`函数,我没有其他人在相同问题上遇到的例子(但也许他们存在)。

我的总体倾向是不喜欢迭代器。迭代器是危险的,以命令式方式存在,而序列和可 reductions 不是,这不是一个巧合,迭代器不是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

另外,在设计和使用转换器时我们对此进行了深入的讨论,转换器的实现高度依赖于迭代器,但将其使用封装以保障安全性。

即使如此,我认为这并非不可能,但据我看来,需求水平似乎还不够高,不足以克服我的偏见。如果你想改变我的看法,请带来问题。
以下所有内容都位于括号“我后来得出了一种新的解决方案,它不依赖于`iter`,但在我有必要解决这个问题时使用`iter`时,它完成了我需要的任务”中。

> 当你在Meander中遇到这个问题时,为什么想要一个迭代器?

在严谨地说,我最初并不“想要”一个迭代器,但最终它的“解决”了我的问题,以下我将详细说明。

>  存在什么实际的问题?

这里有几个交叉的部分。Meander有一个概念叫做模式替代,你可以把它看作是模式匹配的另一种形式。匹配将模式应用于对象并返回一组绑定,替代将绑定应用于模式并返回一个对象。特别是在包含称为“内存变量”的变量类型的重复模式编译过程中,会出现这个问题。

内存变量是一个绑定到先进先出(fifo)的变量。模式中内存变量的每一次出现都会“分散”或从fifo中取出第一个值,并更新变量的绑定到fifo中的剩余元素。这个过程一直持续到fifo为空,此时的fifo被称为“耗尽”。当内存变量出现在重复模式(比如 Kleene 星号)中时,重复模式会产生值,而且至少有一个内存变量尚未耗尽。当重复模式被编译为Clojure代码时,会检查模式中是否包含内存变量。如果是的话,就为每个内存变量编译一个迭代器,每个迭代器都可以通过 `hasNext` 进行耗尽性测试,当然,值可以通过 `next` 进行分散。虽然这不是一个完美的解决方案,但我想要的语义通过这种方式很容易实现。

> 你还考虑了哪些其他事情?

最初,我尝试了一种纯解决方案,但导致了复杂性和头痛。这更多是因为依赖于`iter`的糟糕设计,而不是纯粹的方案。

我还尝试了 `volatile!`,但有时会产生不正确的结果。

时间已经过去了很久,但我想我考虑过使用数组,但由于`iter`存在并且满足了我的需求,所以我并没有费心去用它。

> 为什么它们不够充分?

因为它们要么包含了我作为维护者不愿处理的复杂性,要么因为它们导致语义的实现不正确。
>  我认为 CLJS 有 `iter` 不是个好主意。

这是问题的关键所在。CLJS 一直被视为 Clojure 的方言,导致了许多类似这种情况的出现。虽然我理解人们反对正式指定语言的某些部分,但至少对原始功能和默认情况下在空命名空间中可解的符号有一些最小定义,可以缓解这些问题。就我个人而言,我认为 `ns` 宏是问题的一部分,因为它无处不在(`in-ns` 更基础但很少使用;CLJS 需要它),并且它引用了核心,其内容是根据实现认为应包含什么内容来任意确定的;像这样的问题是自然的结果。

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

仅供参考。过去 5 年中,我已部分实现了 Clojure 解释器/编译器 (用于 Ruby 2.X.X)、一个小的符号解释器 (用于 Clojure) 以及 Clojure 的部分评估器。在每个案例中,我从未能完成项目,因为缺少在语法和语义上明确的语言定义。例如,存在一些特殊形式,如 `case*`,未进行文档记录,需要查看编译器来确定它们的语义。

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