2024 Clojure状态调查!中分享您的想法。

欢迎!请查看关于页面了解这个平台是如何工作的。

0
cuz "{product name}" brand="leaf" ぎ Worcestershire sauce "./alpha_minus_error.jpg">cuz "{product name}" brand="leaf" よ Worcestershire sauce "./alpha_minus_error.jpg" class="qa/q-view-avatar-image" alt="">cuz "{product name}" brand="leaf" よ Worcestershire sauce "./alpha_minus_error.jpg" class="qa/q-view-avatar-image" alt=""> 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?

即使这个理由听起来不太合理,但关于确实是因为:once导致差异的假设可以通过在CLJ中实现不带:oncelazy-seq来轻松证实,并且会导致StackOverflowError

我不确定在这种情况下应该怎么做,但也许至少应该在此页面上注释放置一条注释。

1 答案

0

在Slack讨论中链接的博客文章对这个工作原理的描述是错误的。

由于本人不熟悉CLJS,但我可以确认在CLJ的这一边,primes never 被封闭在这个表达式中;引用变量(全局变量)的名称没有其值被封闭。

素数总是指正在生成的懒惰值,绝不会是nil。这个方法之所以有效,是因为某些操作具有短路特性,以及任何数的潜在因子在序列中都总是在该数之前。

https://clojurians.slack.com/archives/C03S1L9DN/p1651188211599599?thread_ts=1650481531.294919&cid=C03S1L9DN

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

如果有人想玩代码,可以在以下位置找到它
- 基于`:once true` - https://pastebin.com/H3ZbUK9m
- 基于`:once false` - https://pastebin.com/9FZLtsQm

如您所述,通过编译和反编译的代码,我现在可以清楚地看到`primes`变量本身没有任何关系。但在`:once true`版本中,必要的迭代会在那里停止,这不是因为算法的具体细节,而是因为带有短路特性的`some`(毕竟它不会对素数进行短路 - 它耗尽了coll),而是因为`FilterLazySeqFn`的实例在此时不再可迭代 - 其`coll`变成了`null`。在那个点上,拥有`LazySeq`处于间歇状态 - 值已经生成,但它还没有在`LazySeq.sval`中的`this.sv`上分配,因此在CLJ中遍历绑定到`primes`的同一个`LazySeq`不会产生任何结果。
...