[racket] macro for broken abstraction?

From: Gustavo Massaccesi (gustavo at oma.org.ar)
Date: Tue Dec 9 07:40:47 EST 2014

I think that you want to capture the variables. I made a simplified
example. The other variables (for example "hidden") are not capture,
as usual.

;--- boilerplate.rkt ---
#lang racket
(define (call-with-?? proc) (proc)); just for the example
(define exn:fail:network? exn:fail?); just for the example
(provide with-db)
(define-syntax with-db
  (lambda (stx)
    (syntax-case stx ()
      [(_ body ...)
       (with-syntax ([err-msg (datum->syntax stx 'err-msg stx stx)]
                     [db (datum->syntax stx 'db stx stx)])
         ; --- start boilerplate
         (syntax-protect
          (syntax/loc stx
            (let/ec fail-network
              (with-handlers [(exn:fail:network? (lambda (e)
                                                   (set! err-msg
"database connection error")
                                                   (fail-network)))]
                (define db 5)
                (define hidden 7)
                (call-with-?? (lambda () body ...))))))
       ; --- end boilerplate
         )])))

;--- main.rkt
#lang racket
(require "boilerplate.rkt")
(define err-msg #f)
(define hidden "BU")
(with-db
  (displayln db); ==> 5
  (displayln hidden); ==> "BU"
  (/ 1 0)
  (display "never"))
(when err-msg
  (error 'error err-msg))

;-------

There is a big red warning in the Racket documentation about macros
with variable capture, but I can't find it. It recommends to use
syntax-parameters, but the syntax parameter can't change the value of
err-msg after the macro body had finished. The problem is that if you
use the with-db macro inside a with-super-db macro, the results are
strange.

Gustavo


On Mon, Dec 8, 2014 at 8:45 PM, George Neuner <gneuner2 at comcast.net> wrote:
> 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
>
>
> ____________________
>  Racket Users list:
>  http://lists.racket-lang.org/users

Posted on the users mailing list.