请在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
_Comment made 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),调用成功后,body-fn 被设置为 null(意味着已实现)。

:once 标志仅影响 [编译器这一部分|https://github.com/clojure/clojure/blob/d0c380d9809fd242bec688c7134e900f0bbedcac/src/jvm/clojure/lang/Compiler.java#L4701]。在函数调用过程中,某些字段被设置为 nil,这完全合理,因为它可以让垃圾收集器回收对象,但这种操作只应在函数成功完成后进行。这可以改变吗?
0

评论由:hank

对于第 1 条评论中描述的 '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)))))

`

因为下游的函数都是慢速计算的 deref 类型,它们会缓存其结果(更多lazy-seq、delay、future、promise),所以 InterruptedException 只可能在第一次评估期间发生,而尾调用优化(它设置为设置为 nil 的封闭变量,值得在这里阅读:https://clojure.org/lazy)只会在第二次调用时发生。第一次调用会创建一个捕获序列 's' 头的 fn,但这个 fn 并没有被保留,因为它没有被返回。

当我想要中断、可重启的序列评估时,我会使用这个特殊的 map 版本(以及基于懒惰序列类似重写的其他函数,如 iterate)。

0

评论由:hank

与上面的黑客技术相反,通过使用懒汉 Y-组合子实现 map 以及所有其他组合器,并在 lazy-seq 定义中删除 :once 元数据标签可以正确地解决问题。由于触发由 :once 元数据标签触发的清理封闭变量的编译器某种程度上的 Hack 只是对可以使用 Y-组合子实现的递归情况,因此也不再需要了。

0
by

评论人:alexmiller

关于前述延迟的参考,已在 CLJ-1175 中解决(并已解决),目前正在等待最终审批。

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