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

欢迎!请查看 关于 页面了解此功能的更多信息。

0
Clojure

当体中包含宏并抛出异常时,lazy-seq 似乎评估不一致。第一次评估抛出异常,随后的评估返回空序列。

示例代码

`
(defn gen-lazy []
(let [coll [1 2 3]]

(lazy-seq
  (when-let [s (seq coll)]
    (throw (Exception.))))))

(def lazy (gen-lazy))

(try
(println "lazy:" lazy)
(catch Exception ex

(println ex)))

(try
(println "lazy, again:" lazy)
(catch Exception ex

(println ex)))

`

理论上两次都应该抛出异常,但实际上只在第一次抛出。一般来说,表达式不应该根据是否已经评估过而评估为不同的结果。

当移除闭包...

`
(defn gen-lazy []
(lazy-seq

(when-let [s (seq [1 2 3])]
  (throw (Exception.)))))

`

... 或者移除 when-let 宏...

`
(defn gen-lazy []
(let [coll [1 2 3]]

(lazy-seq
  (seq coll)
  (throw (Exception.)))))

`

它就可以正常工作,即一致地抛出异常。这表明闭包和宏之间存在某种交互作用。这种特定的组合在 'map' 函数中使用。

另见:https://groups.google.com/forum/?fromgroups=#!topic/clojure/Z3EiBUQ7Inc

9 答案

0

评论由:hank 制作

注意。如果这有关系,我使用这个功能的主要用例是中断缓慢评估的 'map' 表达式的评估(抛出 InterruptedException),因为我对此不再感兴趣。然后稍后再次重新评估它,因为我对结果感兴趣,然而由于上述错误,懒序列终止而不是从上次离开的地方重新开始。

(更新:此用例类似于 Jetty 所执行的类似替代继续操作(RetryRequest 运行时异常)或 Clojure 本身在进行 STM 事务时抛出的 RetryEx 异常。)

0

评论由:hank 制作

根据 Christophe 的说法,这与 CLJ-457 有关。他的补丁也修复了这个问题。

0

评论由:hank 制作

对不起,Christophe 的补丁在这里不起作用。它会通过提前抛出异常来避免对 LazySeq 重复评估。然而,LazySeq 在第二次评估时可能按预期正常运行,因为导致异常的情况是瞬时的。根据上面的评论,可能会中断一次评估并抛出 InterruptedException,但不会在第二次。

在我看来,也需要对闭包和宏的观察进行解释。

0

评论由:hank 制作

进一步了解:'delay' 表现出相同的行为,并且是一个更简单的案例来检查。宏疑虑是一个烟幕弹:如以下所示,实际上是闭包中的变量神奇地变成 nil,when-let 宏只是将其变成整个表达式的 nil。

`
(def delayed
(let [a true]

(delay
  (print "a=" a)
  (throw (Exception.)))))

(try
(print "delayed 1:")
(force delayed)
(catch Exception ex (println ex)))

(try
(print "delayed 2:")
(force delayed)
(catch Exception ex (println ex)))
`

打印

delayed 1:a= true#
delayed 2:a= nil#

0
评论来自:hank_

以上导致了一些棘手的后果,例如:以下表达式在第一次评估时抛出异常,并在后续评估时输出“w00t!”。


(def delayed
  (let [a true]
    (delay
      (if a
        (throw (Exception.))
        "w00t!"))))


尝试这样做


(try
  (print "delayed 1:" )
  (println (force delayed))
  (catch Exception ex (println ex)))

(try
  (print "delayed 2:")
  (println (force delayed))
  (catch Exception ex (println ex)))


结果如下
delayed 1:#<Exception java.lang.Exception>
delayed 2:w00t!

此代码显示问题确实与所怀疑的 :once 标志有关。


(def test-once
  (let [a true]
    (^{:once true} fn* foo []
        (println "a=" a)
        (throw (Exception.)))))


调用该 fn 两次将显示 'a' 从 'true' 变为 'nil',尝试这样做


(try
  (print "test-once 1:")
  (test-once)
  (catch Exception ex (println ex)))

(try
  (print "test-once 2:")
  (test-once)
  (catch Exception ex (println ex)))


结果如下
test-once 1:a= true
#<Exception java.lang.Exception>
test-once 2:a= nil
#<Exception java.lang.Exception>

移除^{:once true}后不会发生这种情况。现在可以辩称上述fn被调用了两次,这正是用:once标志装饰时不应该做的事情,但我认为不成功的调用不应计入:once标志的调用次数。延迟和lazy-seq宏也同意我的观点,因为如果身体表达式的评估抛出异常,那么得到的对象不被认为是实现的(如函数realized?所示),因此,延迟/lazy-seq的重新评估会重复实现/评估身体。

在上述代码的第一次评估之后,尝试使用(realized? delayed)。在实现中可以看出这一点,例如[这里clojure.lang.Delay的来源|https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Delay.java#L33](LazySeq类似),在调用返回*成功*之后,body-fn设置为null(表示完成)。

:once标志只影响[编译器这部分|https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]。在函数调用过程中,某个字段被设置为null,这是让垃圾回收器收集对象的好理由,然而这只应在函数成功完成后才这么做。这能改变吗?
0

评论由:hank 制作

对于1st评论中描述的'map'函数的情况,一种规避方法是:原始的map函数,如果你移除几个coll的情况,针对分块序列的性能提升以及强制coll参数变成序列,看起来是这样

`
(defn map [f s]
(lazy-seq

(cons (f (first s)) (map f (rest s)))))

`

在我的规避方法中,我对f两次进行评估

`
(defn map [f s]
(lazy-seq

(f (first s))
(cons (f (first s)) (map f (rest s)))))

`

因为慢速评估的下游函数全是deref类型的,会缓存其结果(更多lazy-seqs、delays、futures、promises),InterruptedException只有在第一次评估期间才会发生,而尾调用优化(设置封闭变量为nil,它值得在这里阅读:https://clojure.org/lazy)只在第二次调用时发生。第一次仍然创建一个捕获序列's'头部 的fn,但这个并不是被持续保留的,因为它没有被返回。

当我希望中断、可重启的序列评估时,我使用这种特殊的map版本(以及基于lazy-seq的其他类似重写的函数,如iterate)。

0

评论由:hank 制作

可以使用映射和所有其他组合器用lazy Y-combinator实现,并在lazy-seq定义中删除:once元数据标签来正确解决这个问题。由于编译器的radius-hack(由于:once元数据标签触发的清除封闭变量的操作)主要是基于可以使用Y-combinator实现的递归,因此这些也不会再需要了。

0

评论者:alexmiller

关于上述延迟参考的问题,这已在CLJ-1175中解决并待最终审核。

0
参考: https://clojure.atlassian.net/browse/CLJ-1119 (由 alex+import 报告)
...