Hi all,
I've written my first macro: with-open-flexi!
(defmacro with-open-flexi!
" Just like with-open except it takes closing expression instead
of assuming it to be .close
Macro definition :
(with-open-flexi! [] body) expands to -
(do body)
(with-open-flexi! [obj1 init1 close1 more] body)) expands to
(let [obj1 init1]
(try
(with-open-flexi! [more] body)
(finally close1)))
Note: It is your responsibility to ensure -
1. Count of elements in the bindings is a multiple of 3
2. 0th, 3rd, 6th, 9th ... index of binding is a symbol
example usage -
(with-open-flexi! [conn1 (API1/Conn args) (API1/Disconn conn1
args)
conn2 (API2. args) (API2/Disconn
args conn2)
conn3 (API3. args) (.disconn
conn3)]
(... use conn1, conn2, conn3 ...)
42)
"
[bindings & body]
(if (= 0 (count bindings))
`(do ~...@body)
(let [[o i c & more] bindings]
`(let [~o ~i]
(try
(with-open-flexi! [...@more] ~...@body)
(finally ~c))))))
Tested it :
=> (with-open-flexi! [o1 (println "i1") (println o1 "c1")
o2 (println "i2") (println o2 "c2")]
(println "body1") (println "body2") 42)
i1
i2
body1
body2
nil c2
nil c1
42
Please let me know if there are any bugs / gotchas / improvements etc.
- Thanks
On Apr 21, 8:04 pm, "Mark J. Reed" <[email protected]> wrote:
> The main thing about Perl6 in this case is that the catch/finally blocks are
> inside the same scope as the try. But that's true in Clojure as well!
> The difference is that Clojure's try is not itself a lexical binding scope;
> you have to wrap one around it or within it via let. That's why I thought a
> combination of try and let would be useful, if only as syntactic sugar.
>
>
>
> On Wed, Apr 21, 2010 at 10:43 AM, ka <[email protected]> wrote:
> > Thanks all for replies.
>
> > Laurent, Alex you guys are right, the problem is only with aesthetics
> > of nesting / boilerplate. The nesting implementation semantically
> > expresses exactly what is required.
>
> > The with-cleanup macro seems really neat. Guess I'll learn macros
> > first and try to implement one.
>
> > One more interesting perspective to exceptional handling is the way
> > Perl 6 is doing it -
> >http://feather.perl6.nl/syn/S04.html#Exception_handlers
>
> > See this -
>
> > {
> > my $s = '';
> > die 3;
> > CATCH {
> > when 1 {$s ~= 'a';}
> > when 2 {$s ~= 'b';}
> > when 3 {$s ~= 'c';}
> > when 4 {$s ~= 'd';}
> > default {$s ~= 'z';}
> > }
>
> > is $s, 'c', 'Caught number';
> > };
>
> > Thanks!
>
> > On Apr 21, 7:05 pm, Alex Osborne <[email protected]> wrote:
> > > ka <[email protected]> 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
>
> > > --
> > > You received this message because you are subscribed to the Google
> > > Groups "Clojure" group.
> > > To post to this group, send email to [email protected]
> > > Note that posts from new members are moderated - please be patient with
> > your first post.
> > > To unsubscribe from this group, send email to
> > > [email protected]<clojure%[email protected]>
> > > For more options, visit this group athttp://
> > groups.google.com/group/clojure?hl=en
>
> > --
> > You received this message because you are subscribed to the Google
> > Groups "Clojure" group.
> > To post to this group, send email to [email protected]
> > Note that posts from new members are moderated - please be patient with
> > your first post.
> > To unsubscribe from this group, send email to
> > [email protected]<clojure%[email protected]>
> > For more options, visit this group at
> >http://groups.google.com/group/clojure?hl=en
>
> --
> Mark J. Reed <[email protected]>
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to [email protected]
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> [email protected]
> For more options, visit this group
> athttp://groups.google.com/group/clojure?hl=en
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en