请在 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 本身在与 RetryEx 异常争抢 STM 事务时的行为。)

0

评论由:hank 提供

根据Christophe的说法,这与CLJ-457相关。他的补丁也解决了这个问题。

0
by

评论由:hank 提供

对不起,Christophe的补丁在这里不起作用。它通过提前抛出异常来避免.evaluta LazySeq第二次。然而,由于引起异常的情况是临时的,LazySeq第二次评估可能会正确。按照上面的评论,一个评估可能被中断,第一次抛出InterruptedException,但第二次没有。

在我看来,关于闭包和宏的观察需要解释。

0
by

评论由: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
by
_评论者: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.)))))


调用该函数两次将显示'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类似),在体fn调用成功返回后将其设置为null(表示实现)。

`:once`标志只影响[编译器的这部分|https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]。在函数调用过程中,在该处将某个字段设置为nil,这确实是一个非常好的原因,因为这样可以让垃圾收集器收集对象,然而,这应该在函数成功完成后才能进行。这可以改变吗?
0
by

评论由:hank 提供

1st评论中描述的'map'函数的情况的解决方法如下:原始map函数,如果你这样做取走几个coll的情况,对于块序列的性能提升和强制coll参数为seq,其看起来是这样的

`
(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)))))

`

因为所有下游函数都是慢速评估的函数,它们都会缓存其结果(更多的lazy-seqs,delays,futures,promises),InterruptedException只有在第一次评估时才会发生,而尾调用优化设置闭包变量为nil(值得在这里阅读这篇文章:https://clojure.org/lazy)只发生在第二次调用时。第一次仍然创建了一个捕捉序列's'头部的函数fn,但这个没有被保留,因为它没有被返回。

当我想进行可中断的、可重新开始的序列评估时,我会使用这个特殊的map版本(以及其他类似基于lazy-seq重写的函数,如iterate)。

0
by

评论由:hank 提供

与其以上黑客一样,实现map以及所有其他组合器使用懒Y组合器,并在lazy-seq定义中删除`:once`元数据标签,这样可以正确修复问题。由于编译器为了清除由`:once`元数据标签触发的闭包变量的hacks,基本上只为可以以Y组合器实现的递归情况,所以也不再需要了。

0

评论者:alexmiller

关于上述延迟问题,已在该 CLJ-1175 中涵盖(并解决),目前等待最终审核。

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