2024年Clojure状态调查中分享你的想法!

欢迎!有关更多关于该功能的信息,请参阅关于页面。

0
ClojureScript

关于上下文,开始于Slack:https://clojurians.slack.com/archives/C03S1L9DN/p1650480331191719

这段代码在CLJ上运行正常,但在CLJS上会因StackOverflowError失败

(def primes (remove
             (fn [x]
               (some #(zero? (mod x %)) primes))
             (iterate inc 2)))

在此情况下,CLJ和CLJS之间重要的区别是,在前者中,lazy-seq会导致整个身体的函数包装^:once

它之所以这样工作,是因为在上面的代码中,内部primes是一个封闭的值,它变成了一个本地。当lazy-seq的体函数(带有^:once)第二次被调用时,其本地为nil,因此内部primes也是nil。迭代停止。
在CLJS中,没有:once,也没有本地清除,因此内部primes永远不会是nil,在没有调用那个zero?的情况下,继续执行到那个fn中。

即使推理听起来不太合理,但关于是确实::once导致了差异的假设可以很容易地通过在CLJ中不带^:once实现lazy-seq并通过注意到这会导致StackOverflowError来证实。

我不明白在这种情况下做什么,但也许至少应该在https://script.clojure.org/about/differences页面上对其做笔记。

1 答案

0

在Slack讨论中链接的博客文章关于这一点是错误的。

我无法就cljs方面的事情进行评论,但在clj方面,在这个表达式中primes永远不会被封闭,引用全局变量的名称没有其值的封闭。

primes总是指向正在生成的延迟,而没有指向nil。这个工作原理是因为some的短路性质,以及任何可以整除一个数的数总是出现在序列中的数之前。

by
那么,省略`:once`后行为的变化该如何解释呢?
by
所以,我决定深入了解,编写了`:once true`和`:once false`版本,编译后反编译,将其转换为可读的尽可能简化的Java代码,并测试以确认行为相同。这两个Java版本之间的唯一区别是`:once true`版本会清除代表`fn*`传递给`LazySeq`的类的`this.coll`,而`:once false`版本则不会清除它(对于`pred`也是如此,但出于简化代码的考虑我移除了它,因为它不影响任何事情)。

如果有人想尝试这段代码,位置如下
- 根据`:once true` - https://pastebin.com/H3ZbUK9m
- 根据`:once false` - https://pastebin.com/9FZLtsQm

正如您所说,根据编译/反编译的代码,我现在可以清楚地看到`primes`变量本身没有任何关系。但`:once true`版本在需要时停止迭代,这并不是由于算法的具体性质,也不是因为短路`some`(毕竟,它不会在素数上短路——它耗尽集合),而是因为这个`FilterLazySeqFn`实例本身停止可迭代——它的`coll`变成了`null`。当时的拥有`LazySeq`处于未知状态——值已经被产生,但尚未分配到`LazySeq.sval`中的`this.sv`,所以遍历CLJ中绑定到`primes`的同一`LazySeq`不会产生任何东西。
...