Provides safer versions of delay and lazy-seq that ensure the function calculating their cached values is only called once while preserving their locals-clearing capabilities.
The tradeoff being they now cannot be recursively dereferenced, instead now throwing an exception instead of risking executing locals-cleared garbage.
This article explains how locals clearing enables tail calls to potentially to inherit strong references to their arguments from closed over locals. https://clojure.org/reference/lazy#_dont_hang_onto_your_head
Fixes https://clojure.atlassian.net/browse/CLJ-2861
There are two ways a ^:once function can be called in a delay or lazy-seq:
The first issue can be handled by first checking if the realize lock is already held by the current thread.
if(l.isHeldByCurrentThread()) { throw Util.sneakyThrow(Util.runtimeException("Recursive delay dereference")); }
We do not do this because we don't have access to the locks of Delay and LazySeq.
The second issue could be better supported by the Clojure compiler. The form (^:once fn [] (recur)) probably should throw a compile-time error.
Instead, we take advantage of locals clearing to detect recursive calls:
(let [x true] (^:once fn* [] (assert x) (recur)))
This has a runtime cost. An alternative could be move the body out of tail position, which would result in a compile-time error:
(^:once fn* [] (let [x (recur)] x))
Here we use the assertion approach to handle all cases for portability and compatibility.
Provides safer versions of delay and lazy-seq that ensure the function calculating their cached values is only called once while preserving their locals-clearing capabilities. The tradeoff being they now cannot be recursively dereferenced, instead now throwing an exception instead of risking executing locals-cleared garbage. This article explains how locals clearing enables tail calls to potentially to inherit strong references to their arguments from closed over locals. https://clojure.org/reference/lazy#_dont_hang_onto_your_head Fixes https://clojure.atlassian.net/browse/CLJ-2861 There are two ways a ^:once function can be called in a delay or lazy-seq: 1. the thread that acquires the lock to call the body then realizes the same object by calling the body. 2. the body has a (recur) call in tail position. The first issue can be handled by first checking if the realize lock is already held by the current thread. if(l.isHeldByCurrentThread()) { throw Util.sneakyThrow(Util.runtimeException("Recursive delay dereference")); } We do not do this because we don't have access to the locks of Delay and LazySeq. The second issue could be better supported by the Clojure compiler. The form (^:once fn [] (recur)) probably should throw a compile-time error. Instead, we take advantage of locals clearing to detect recursive calls: (let [x true] (^:once fn* [] (assert x) (recur))) This has a runtime cost. An alternative could be move the body out of tail position, which would result in a compile-time error: (^:once fn* [] (let [x (recur)] x)) Here we use the assertion approach to handle all cases for portability and compatibility.
(delay & body)
Takes a body of expressions and yields a Delay object that will invoke the body only the first time it is forced (with force or deref/@), and will cache the result and return it on all subsequent force calls. See also - realized?
Throws if dereferenced recursively. Explicitly disallows :pre/:post map.
Takes a body of expressions and yields a Delay object that will invoke the body only the first time it is forced (with force or deref/@), and will cache the result and return it on all subsequent force calls. See also - realized? Throws if dereferenced recursively. Explicitly disallows :pre/:post map.
(lazy-seq & body)
Takes a body of expressions that returns an ISeq or nil, and yields a Seqable object that will invoke the body only the first time seq is called, and will cache the result and return it on all subsequent seq calls. See also - realized?
Throws if realized recursively. Explicitly disallows :pre/:post map.
Takes a body of expressions that returns an ISeq or nil, and yields a Seqable object that will invoke the body only the first time seq is called, and will cache the result and return it on all subsequent seq calls. See also - realized? Throws if realized recursively. Explicitly disallows :pre/:post map.
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close