[racket] macro for broken abstraction?
Hi all,
This is more curiosity than a problem.
I am writing a web service which uses a database and there is a fair bit
of boilerplate exception handling code which is needed in just about
every servlet. It takes up a lot of page and indentation space in the
source and just offends my sensibilities. I have created a macro to
condense the visual, but I'm not entirely happy with it because of how
it deals with interactions among the template, the body and the
surrounding code.
As used inline, everything I am doing fits into the following pattern:
(let [
(err-msg #f)
]
:
; --- start boilerplate
(let/ec fail-network
(with-handlers [
(exn:fail:network?
(lambda (e)
(set! err-msg "database connection error")
(fail-network)))
]
(let [
(db (connect-database))
]
(let/ec fail-sql
(with-handlers [
(exn:fail:sql?
(lambda (e)
(let [(info (exn:fail:sql-info e))]
(set! err-msg (cdr (assoc 'message info)))
(fail-sql))))
]
(call-with-transaction db
(lambda ()
:
< body ... >
:
)
#:isolation 'repeatable-read)
)
(disconnect db))
)))
; --- end boilerplate
; err-msg needed here
)
The problems, of course, are that "err-msg" is external to the
boilerplate code, and "db" is internal to it. The exception handlers
in the boilerplate need access to "err-msg" (or whatever the actual
variable name might be) and code in the <body> needs to reference "db"
which is only defined within the scope of the macro.
I've gotten it to work with syntax-rules by passing the problem names in
as arguments:
(define-syntax with-database
(syntax-rules ()
((with-database-connection db err-msg body ...)
... )))
knowing that I can use any names externally or in the body (the above is
just example).
This works, but having to pass in the (name of the) required error
status variable is ugly. I know I can add a keyword (or several) to
handle the status variable, but reasonable syntaxes for it are
convoluted. Something like "with-database-connection <JOE> sending
errors to <BOB> ..." obviously is possible, but is unwieldy (and
un-Scheme-like [ un-Schemely? ] ) - it looks more like Smalltalk or
Lisp's loop language than it does like Scheme. 8-)
I suppose I could require to pass in exception handler functions rather
than a target for their output, but that also is unwieldy and error
prone because the functions have to be inserted at different nesting levels.
Is there some idiomatic way people normally handle this kind of broken
abstraction? I've searched a bit trying to find a discussion of
something like this, but so far haven't found anything enlightening.
Thanks,
George