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

而“下一更递归”片段(在数学内容中)在 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`,因此它可能是为了性能/垃圾回收原因,正如其他回答所指出的。
0

我建议尝试使用 Criterium 库对您正在考虑的当前版本和修改后的函数进行性能基准测试,并查看它是否会使代码更快: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纳秒。

但无论如何,这个问题并不完全是关于性能的。
它是关于算法中已经完成的计算的无效重复。
...