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

欢迎!请查看 关于 页面以了解该服务的更多信息。

+1 投票
序列

我在 clojure.core 的几个地方看到了以下模式

(if (next more)
  (recur ... (next more))
  ...)

其中在调用 `recur` 之前两次调用 next

这是“按设计”还是只是未被注意到的代码?

我期望一个更高效实现,例如:

(if-let [next' (next more)]
  (recur ... next')
  ...)

3 个答案

+1 投票

这可能是为了避免“持有序列头部”的风险。一般来说,你希望在递归调用之前,已经被消耗的部分序列可以被垃圾收集。

“持有头部”问题适用于使用 `recur` 进行“尾递归”调用吗?
+1 投票

编辑

这应该是核心中函数定义的顺序问题吗?

浏览核心文件 core.clj 时,发现宏 `if-let` 实际是在第 1841 行定义的

https://github.com/clojure/clojure/blob/653b8465845a78ef7543e0a250078eea2d56b659/src/clj/clojure/core.clj#L1841

而在数学内容中的"下一更多的递归下一更多的"片段(在 `let` 或 `if-let` 存在之前)被使用(第 1055 行)

https://github.com/clojure/clojure/blob/653b8465845a78ef7543e0a250078eea2d56b659/src/clj/clojure/core.clj#L1055

`defmacro if-let` 之后的代码实际上使用的是你期望的模式(第 3690 行)

https://github.com/clojure/clojure/blob/653b8465845a78ef7543e0a250078eea2d56b659/src/clj/clojure/core.clj#L3690

我认为这应该是一个常规的单次遍历编译,就像是常规的 Clojure 代码一样。函数必须在使用前定义,无论是宏还是 defns。

我不确定为什么顺序必须这样,可能和启动过程有关。

但是 `let` 是一个特殊形式,并且有 `:special-form true` 属性,因此可能确实是为了性能/垃圾回收原因,正如其他答案所指出的。
0

我建议尝试与当前修改版本的函数进行一些性能基准测试,看看它是否会导致代码运行更快,使用 Criterium 库在 Clojure/Java 上进行性能测量: https://github.com/hugoduncan/criterium

我本人没有做过此类测量,但 HotSpot JIT 编译器有时能做些令人惊奇的事情。

我同意。性能问题很复杂。

你可能还会对这个有些相似的议题感兴趣
https://ask.clojure.org/index.php/8361/changing-some?show=8361#q8361
当然我提问前已经测试过了。

  (= 1 1 1 1 1 1)

在我的PC上快了15%。
单 `(next more)` 约为10 ns。

但无论如何,这个问题并不是直接关于性能的。
它关于算法中不必要的重复计算。
...