is it possible to create a linting rule to prevent head retention in certain contexts?
can you give code examples?
(defn defaulting [fallback expr]
(if (some? expr) expr fallback))
(let [v (apply f args)] ;; <-- very large sequence
(defaulting v ;; <-- oops, head retention
(cond
(not (apply cacheable-args? args))
(log/info "non-cacheable args; NOT writing" k)
(not (cacheable? v))
(log/info "non-cacheable result; NOT writing" k)
:else
(do (cache-write! k v) ;; <-- writes large sequence, causing OOM
(log/info "wrote" k)
(cache-read k)))))just looking for strategies to avoid head retention of certain expressions, the fix here is to throw away the defaulting expression, and return v in each of the first two cond clausese
i guess i'm out of my element because i don't know how you'd possibly figure out that there's head retention in that
how would clj-kondo know that v is a seq of some large size? what if v was a map or a java date? i guess you'd have to rely on the inferred return type of f?
maybe a good rule of thumb is to not let laziness escape a function? (not sure)
i'll have to think about it more, wanted to check in case it's been solved before
generally, if a giant sequence can't fit in memory, i need to mark the source and the sink
so the v binding is the source, and the sink is cache-write!
like Noah hints, I think this is hard to do using static analysis
it does kinda remind of of lifetime annotations in Rust, where just one function can take ownership of something
maybe the sink has to be in something like a tail position
It is enforcing linear usage, so linear types would be something like it, rust's lifetimes are a sort of superset of linear types
Linear types also warn if you don't use it at least once, so it would catch not realized laziness too
hey @ambrosebs can typed clojure do linear types?
I wrote a let-drain macro that statically checks for any symbolic references that might retain the head of a sequence at the expression tagged with ^:draining. Not perfect, but communicates intent.