ka <
sanc...@gmail.com> writes:
> The whole code gets cluttered with all these try finally (and one
> catch) statements.
>
> (try
> (let [conn1 (API1/getConnection ..)]
> (try
> (let [conn2 (API2/getConnection ..)]
> (try
> ( ........... Do something with conn1 conn2 ............)
> (finally
> (API2/closeConnection conn2))))
> (finally
> (API1/closeConnection conn1))))
> (catch Exception ex (.printStackTrace ex)))
I guess the main difference in this compared to your java example is the
levels of nesting. This may look messy but it's semantically exactly
what you're trying to express.
> The macro solution looks good. But with 2 different APIs for 2
> connections, I would need to write 2 macros right?
>
> (defmacro with-api1-connection [conn-sym arg1 arg2 & body]
> `(let [~conn-sym (API1/getConnection ~arg1 ~arg2)]
> (try
> ~@body
> (finally (API1/closeConnection ~conn-sym)))))
>
> (defmacro with-api2-connection [conn-sym arg1 arg2 arg3 & body]
> `(let [~conn-sym (API2/getConnection ~arg1 ~arg2 ~arg3)]
> (try
> ~@body
> (finally (API2/closeConnection ~conn-sym)))))
You could make things more general:
(with-cleanup [conn1 (API1/getConnection ...) API1/closeConnection
conn2 (API2/openConnection ...) #(.disconnect %)]
...)
I'll leave implementation as an exercise, it's not much more complicated
than the previous ones, the main trick would just be to make the macro
recursive, have it expand into:
(let [conn1 (API1/getConnection ...)]
(try
(with-cleanup [conn2 (API2/openConnection ...) #(.disconnect %)]
...)
(finally
(API1/closeConnection conn1))))
I'd probably start with a signature like this:
(defmacro with-cleanup [[sym create cleanup & more] & body]
...)
Take a look at the source for with-open if you get stuck.
> Coming from Java, this would be implemented as -
>
> Connection1 conn1 = null;
> Connection2 conn2 = null;
> try {
> conn1 = API1.getConnection ..;
> conn2 = API2.getConnection ..;
> ...
> }
> catch (){}
> finally {
> if (conn1 != null)
> API1.closeConnection(conn1);
> if (conn2 != null)
> API2.closeConnection(conn2);
> }
>
> I agree that this code doesn't look good from a purist pov, but any
> issues besides that?
The problem here is that this breaks lexical scope, conn1 and
conn2 aren't defined outside their let block. The Java example dodges
this with mutation. Python/Ruby/JavaScript etc dodge it by having
special scoping rules: variables are scoped to functions rather than the
enclosing block.
Clojure's opinion, as I understand it, is that it's not worthwhile
introducing mutation or special scoping rules simply to avoid some
nesting, when we have perfectly good tools (macros) for doing purely
syntactic transformations and removing boilerplate.
There's nothing semantically wrong with nesting, it's just harder
to read. The Clojure idiom for reducing nesting is usually to use a
macro like ->, ->> or with-open to flatten it. In this case those
aren't applicable, so I suggest defining your own.
I'm not sure I phrased that clearly, please let me know if I'm not
making sense. :-)
Alex