On Jun 25, 5:22 pm, Stuart Sierra <
the.stuart.sie...@gmail.com> wrote:
> On Jun 25, 4:01 pm, Graham Fawcett <
graham.fawc...@gmail.com> wrote:
>
> > Thanks, but it's an alternative to (with-open) that I'm looking
> > for. Consider this:
>
> > (last (with-open f
> > (java.io.BufferedReader.
> > (java.io.StringReader. "hello\nworld"))
> > (line-seq f)))
>
> > It will raise an exception, "java.io.IOException: Stream closed",
> > because (with-open) closes the stream before (last) gets a crack at
> > it. What I'm looking for is a way to tell a stream, "look, you've just
> > consumed the last element, so close yourself". With-open's semantics
> > are more like "look, we're leaving the scope of with-open, so close
> > the stream".
>
> Hi Graham,
> I've run into this too, haven't found a perfect solution. Here's one
> thing I've done:
>
> (defn lazy-stream [r] ; r must be a BufferedReader
> (if-let line (. r (readLine))
> (lazy-cons line (lazy-stream r))
> (. r (close))))
>
> But if you don't consume the entire sequence, the stream never gets
> closed.
Thanks Stuart, I like that, it's a good general solution.
On the drive home yesterday, I remembered that PLT Scheme recently
added a feature called "custodians" for fine-grained resource
management:
http://download.plt-scheme.org/doc/371/html/reference/Evaluation_Model.html#(part%20mz:custodian-model)
I don't think custodians can be 'ported' to Clojure directly (creation
of stream objects can occur in Java code, and would not be
automatically aware of the custodian system). But I do like the idea
that one could define a wider scope in which resources could be
managed. Leaving the scope closes the managed resources.
I'm going to read up on the 'custodian' system and see if I can
propose a Clojure-friendly workalike. A quick sketch might be like
this:
*custodian* -- a Var holding the current set of managed resources
(manage f) -- add f to the current custodian
(cleanup) -- close all objects in the current custodian
and some syntax:
(in-custodian ...)
-- establish a new custodian (binding *custodian* to a new,
empty set), and a lexical scope outside of which the managed
resources are closed.
(with n v body)
-- sugar for (let [n v] body), this is optional, but might make
converting (with-open ...) code easier.
Then one might write:
(let [open-stream #(manage (java.io.BufferedReader.
(java.io.StringReader. %)))]
(in-custodian
...
(doseq line (take 3 (open-stream "hello\nworld"))
(print line))
...)
;; at this point, the stream(s) would be closed.
...)
(I put open-stream outside of the in-custodian lexical scope, just to
suggest the dynamic scope of the custodian.)
It deserves more thought, and some good use-cases. But the general
idea of extending the useful scope of a stream wider than (with-open),
while maintaining a "management scope" for it, is a good idea IMO.
Best,
Graham