Correct idiom for looping inside a catch or finally form?

4 views
Skip to first unread message

Constantine Vetoshev

unread,
Sep 13, 2009, 4:05:13 PM9/13/09
to Clojure
I have some code which opens a bunch of resources (say, files), and
needs to make sure they all get closed. Something like this:

(let [files (atom [])]
(try
(open-my-many-files files)
;; the files atom now refers to a vector of open file handles
(do-dangerous-io @files)
(finally
(doseq [file @files]
(.close file)))))

The Clojure compiler does not like this:
error: java.lang.UnsupportedOperationException: Cannot recur from
catch/finally

Does Clojure have a lower-level iteration primitive than loop and
recur, which I can use inside exception handling code? I can obviously
put a letfn inside the finally and use good old recursion, but I feel
uncomfortable doing this without TCO. (Yes, I know I'm likely to run
out of file descriptors before I run out of available stack frames,
but this is still the wrong way to do things.) I would also very much
prefer not to have to write and call out to Java helper classes for
something as trivial as a loop.

Kevin Downey

unread,
Sep 13, 2009, 5:09:38 PM9/13/09
to clo...@googlegroups.com
you are using an atom as a temporary variable. please don't do that.


(doseq [f files]
(with-open [of (open-file f)]
(do-dangerous-io of)))
--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

Constantine Vetoshev

unread,
Sep 13, 2009, 10:30:08 PM9/13/09
to Clojure
Apparently I oversimplified my example. In reality, I am trying to
deal with a bunch of open Berkeley DB cursors which I need to combine.
They cannot be processed serially. I need to ensure that all those
cursors close at the end even if an exception occurs, so I need to
loop over the resulting list or vector of open cursor handles. This
list happens to be stored in an atom, but that is completely
irrelevant to the problem I am trying to resolve: Clojure does not
seem to support iteration inside catch or finally forms.

Thanks,
CV

Richard Newman

unread,
Sep 13, 2009, 10:38:50 PM9/13/09
to clo...@googlegroups.com
> Clojure does not seem to support iteration inside catch or finally
> forms.

Apparently not directly. Splitting the iteration into a separate named
function works, though:

(defn print-seq [foo]
(doseq [x foo]
(println x)))

(let [foo (atom [1 2 3])]
(try
(throw (new Exception "foo"))
(catch Exception e
(print-seq @foo))))


so you could try

(defn close-all-files [files]
(doseq [file files]
(.close file)))

(let [files (atom [])]
(try
(open-my-many-files files)
;; the files atom now refers to a vector of open file handles
(do-dangerous-io @files)
(finally

(close-all-files @files))))

-R

Kevin Downey

unread,
Sep 13, 2009, 11:03:31 PM9/13/09
to clo...@googlegroups.com
user=> (macroexpand '(with-open [x A y Y] do stuff here))
(let* [x A] (try (clojure.core/with-open [y Y] do stuff here) (finally
(. x clojure.core/close))))
user=>

with-open expands to a (try (finally (.close ...)))

Richard Newman

unread,
Sep 13, 2009, 11:18:39 PM9/13/09
to clo...@googlegroups.com, clo...@googlegroups.com
I think the OP's issue was that he has a sequence of things to close,
determined at runtime. with-open itself is no use there, because the
things to close must be known at compile-time.

He is correct that finally and catch do not permit iteration. They do
permit function calls, hence my workaround.

--
Sent from my iPhone.

Constantine Vetoshev

unread,
Sep 13, 2009, 11:44:05 PM9/13/09
to Clojure
On Sep 13, 11:18 pm, Richard Newman <holyg...@gmail.com> wrote:
> I think the OP's issue was that he has a sequence of things to close,  
> determined at runtime. with-open itself is no use there, because the  
> things to close must be known at compile-time.

Exactly.

> He is correct that finally and catch do not permit iteration. They do  
> permit function calls, hence my workaround.

Thank you, Richard. Your suggestion works.
Reply all
Reply to author
Forward
0 new messages