[racket] macro for broken abstraction?

From: Alexander D. Knauth (alexander at knauth.org)
Date: Tue Dec 9 16:19:55 EST 2014

On Dec 9, 2014, at 7:40 AM, Gustavo Massaccesi <gustavo at oma.org.ar> wrote:

> 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.

But this works:
#lang racket
(require racket/stxparam)
(define-syntax-parameter thing #f)
(define x #f)
(syntax-parameterize ([thing (make-rename-transformer #'x)])
  (set! thing 5))
x ; 5

> 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
> ____________________
>  Racket Users list:
>  http://lists.racket-lang.org/users



Posted on the users mailing list.