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

欢迎!请参阅 关于 页面以获取更多关于如何使用本站的信息。

+1
序列

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

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

其中 next 在调用 recur 之前两次被调用。

这是“有意为之”还是仅仅是未观察到的代码?

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

(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

而“下一个 more recur 下一个 more”片段(在数学内容中)在使用 letif-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

我认为它与 core.clj 作为常规 Clojure 代码一样进行常规的单次遍历编译。函数必须在你可以使用它们之前进行定义,无论是宏还是 defns。

我不太确定为什么顺序必须是这样的,也许与引导有关。

但 `let` 是一个特殊形式,具有 `:special-form true`,因此可能确实是为了性能/垃圾回收(GC)的原因,正如其他答案所指出的。
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)

在我的电脑上快了15%。
单独的 `(next more)` 大约是 10 纳秒。

不过,这个问题并不完全关乎性能。
它更多地涉及到算法中重复已完成的计算的无效性。
...