Try/Catch & Recur

713 views
Skip to first unread message

Greg Harman

unread,
Dec 14, 2009, 9:07:32 PM12/14/09
to Clojure
I have a function foo which uses tail recursion:

(defn foo [] .... (recur))

I need to do some clean-up work after foo (there is an external
binding that requires some post-foo processing), and this needs to
happen even if foo fails. The naive approach was:

(try (foo)
(finally (clean-up))

However, foo recurs and you can't recur in a try/catch/finally.
(java.lang.UnsupportedOperationException: Cannot recur from catch/
finally)

So, my question is: does anyone have a pattern for exception checking
(this same issue would work if I just wanted to catch an exception in
foo) when the contents of the try block recur?

I could just remove the tail recursion and go for straight recursion
in foo, but there's got to be a better way...

CuppoJava

unread,
Dec 14, 2009, 9:17:11 PM12/14/09
to Clojure
I'm not quite sure about your specific case, but is it possible to
just move the try-catch outside of the recursive function?
Perhaps this is not possible for your specific case, but it seems like
a clean way to handle it, so I would try and massage the problem into
something that can be expressed like this.

Hope that's helpful
-Patrick

eg.
(defn foo [i]
(println i)
(if (zero? i)
(throw (RuntimeException. "Reached Zero."))
(recur (dec i))))

(defn outer-foo [i]
(try
(foo i)
(finally
(println "finished."))))

Adrian Cuthbertson

unread,
Dec 14, 2009, 11:12:28 PM12/14/09
to clo...@googlegroups.com
Hi Greg, here's a sample but realistic pattern of the sort of thing
you're doing;

(import '(java.io BufferedReader FileReader File IOException)
'(bqutils BQUtil))

(defn samp-loop
"Read a csv file containing user records."
[#^String fpath]
(with-open [r (BufferedReader. (FileReader. (File. fpath)))]
(let [; read line 1 for field names
line (.readLine r)
flds (seq (BQUtil/parseCsv line))
nflds (count flds)]
(try
(loop [line (.readLine r) i 0 ]
(when line
(let [ri (seq (BQUtil/parseCsv line))
_ (when (not (= (count ri) nflds))
(throw (IOException. (str "Bad line:"i))))
[uid lname inits] ri]
(println :lname lname :inits inits :uid uid)
(if (= "" uid)
(println "Breaking at line:" i)
(recur (.readLine r) (inc i) )))))
(catch Exception x (prn (.toString x)))
(finally (println "Done:" flds))))))


A few notes;
- BQUtil is home-grown java fn for parsing csv to ArrayLists.
Note the wrapping in seq and then destructuring to get the
field values for each line.
- The outer let (flds) is in scope in the (finally
- The (with-open has its own (finally
- You can throw within the loop - note the throw within
the let using a dummy _ placeholder
- Note the line counter
- Note the technique to "break" when uid = ""
(assuming that's what you wanted to do)

Hth, Adrian.
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Greg Harman

unread,
Dec 15, 2009, 12:07:51 AM12/15/09
to Clojure
Thanks to both of you for the replies. Adrian, I like the in-line loop-
recur. Cuppo, that example is essentially the same one that I was
describing but it was key to helping me, as I saw that it evaluated
fine when I expected it to fail based on my original problem.

Turns out that the problem was my finally clause contained a doseq,
which apparently uses tail recursion internally. Replacing that with a
for will solve the problem for me.

-Greg

CuppoJava

unread,
Dec 15, 2009, 12:24:16 AM12/15/09
to Clojure
I'm glad that everything works now Greg.

Though I have to say that I'm a little suspicious of changing a
"doseq" into a "for" to solve the problem. Off the top of my head, I
can't think of any subtleties that can arise because of doseq and tail
recursion. Is it possible to post a simplified version of your code
that exhibits the same problem? I feel there might be something else
at play here.

-Patrick

Greg Harman

unread,
Dec 15, 2009, 1:08:53 AM12/15/09
to Clojure
Actually, the for didn't work for me either but I believe that was a
lazy evaluation issue. The doseq seems to use internal recursion,
which breaks the try/finally. My final solution was to build up doseq
functionality with reduce. See below:

(defn foo1 []
(try
(println "body")
(finally
(doseq [x (range 3)] (println x)))))

(defn foo2 []
(try
(println "body")
(finally
(for [x (range 3)] (println x)))))

(defn foo3 []
(try
(println "body")
(finally
(reduce (fn [y x] (println x)) () (range 3)))))

- The foo1 definition can't be evaluated b/c of
java.lang.UnsupportedOperationException: Cannot recur from catch/
finally

user=> (foo2)
body
nil

user=> (foo3)
body
0
1
2
nil

Meikel Brandmeyer

unread,
Dec 15, 2009, 1:45:08 AM12/15/09
to Clojure
Hi,

On Dec 15, 7:08 am, Greg Harman <ghar...@gmail.com> wrote:

> Actually, the for didn't work for me either but I believe that was a
> lazy evaluation issue. The doseq seems to use internal recursion,
> which breaks the try/finally. My final solution was to build up doseq
> functionality with reduce.

See also here: http://groups.google.com/group/clojure/browse_thread/thread/2c9f018886e71ee3/87c5698fe30e48a5

Sincerely
Meikel

CuppoJava

unread,
Dec 15, 2009, 9:18:26 AM12/15/09
to Clojure
That's very interesting. I haven't run into this issue before.

One cleanish way to side-step it is to use

(defn foo2 []
(try
(println "body")
(finally
(doall (for [x (range 3)] (println x))))))

Which is perhaps a little cleaner in meaning to the original doseq
than a reduce.
-Patrick

Chouser

unread,
Dec 15, 2009, 9:28:44 AM12/15/09
to clo...@googlegroups.com
Simply moving the body of the 'finally' to a fn syntactically
outside the 'finally' clause itself should always work without
having to reimplement the body itself:

(defn foo-fn []
(let [f #(doseq [x (range 3)]
(println x))]
(try (println "body")
(finally (f)))))

--Chouser

Greg Harman

unread,
Dec 15, 2009, 10:31:26 AM12/15/09
to Clojure
Why does that work? The same recursion happens in the finally. There's
a layer of indirection now, but the doseq was already a layer of
indirection between the finally and doseq's internal recur.

I see from the linked thread above that the basic issue is a known
implementation issue with Clojure to limit complexity - given that,
perhaps doseq (and any other out-of-the-box macros/functions that are
internally implemented with recur, or otherwise trigger that
exception) should be documented as having this effect.

Laurent PETIT

unread,
Dec 15, 2009, 10:44:11 AM12/15/09
to clo...@googlegroups.com
doseq is a macro, not a function, and its expansion expands the loop right in place :

$ java -cp clojure/clojure.jar:clojure-contrib/clojure-contrib.jar clojure.contrib.repl_ln
Clojure 1.1.0-master-SNAPSHOT
1:1 user=> (require 'clojure.contrib.pprint)
nil
1:2 user=> (clojure.contrib.pprint/pprint (macroexpand-1 '(doseq [x (range 0 10)] x)))
(clojure.core/loop
 [seq_9
  (clojure.core/seq (range 0 10))
  chunk_10
  nil
  count_11
  (clojure.core/int 0)
  i_12
  (clojure.core/int 0)]
 (if
  (clojure.core/< i_12 count_11)
  (clojure.core/let
   [x (.nth chunk_10 i_12)]
   (do x)
   (recur seq_9 chunk_10 count_11 (clojure.core/unchecked-inc i_12)))
  (clojure.core/when-let
   [seq_9 (clojure.core/seq seq_9)]
   (if
    (clojure.core/chunked-seq? seq_9)
    (clojure.core/let
     [c__5257__auto__ (clojure.core/chunk-first seq_9)]
     (recur
      (clojure.core/chunk-rest seq_9)
      c__5257__auto__
      (clojure.core/int (clojure.core/count c__5257__auto__))
      (clojure.core/int 0)))
    (clojure.core/let
     [x (clojure.core/first seq_9)]
     (do x)
     (recur
      (clojure.core/next seq_9)
      nil
      (clojure.core/int 0)
      (clojure.core/int 0)))))))
nil
1:3 user=>

HTH,

--
Laurent

2009/12/15 Greg Harman <gha...@gmail.com>

Greg Harman

unread,
Dec 15, 2009, 10:54:05 AM12/15/09
to Clojure
> doseq is a macro, not a function, and its expansion expands the loop right
> in place :

Right. Why does it work (in the finally block) when wrapped up in a
function, but not when doseq is called directly?

Laurent PETIT

unread,
Dec 15, 2009, 11:18:41 AM12/15/09
to clo...@googlegroups.com
doseq expands into a loop.

2009/12/15 Greg Harman <gha...@gmail.com>

--

Chouser

unread,
Dec 15, 2009, 11:31:14 AM12/15/09
to clo...@googlegroups.com
The complexity comes from a recur that is lexically within the
finally. By moving the doseq to a separate function, its
expanded loop/recur is no longer syntactically inside the
finally, so the extra complexity is avoided.

--Chouser
--
-- I funded Clojure 2010, did you?
Reply all
Reply to author
Forward
0 new messages