[plt-scheme] one macro in another?

From: Eli Barzilay (eli at barzilay.org)
Date: Sun Aug 10 21:29:44 EDT 2008

On Aug 10, Jay F Kominek wrote:
> This is roughly what I'm trying to accomplish:
> (define-syntax transaction
>   (lambda (stx)
>     (syntax-case stx ()
>       [(_ expr ...)
>        #`(let ([final-proc #f])
>            #,(define-syntax finally
>                (lambda (stx)
>                  (syntax-case stx ()
>                    [(_ exp (... ...))
>                     (syntax (set! final-proc (lambda () exp (... ...))))])))
>            (let ([v (begin
>                       expr ...)])
>              (when (procedure? final-proc)
>                (final-proc)
>                v)))])))

I'm not sure what you're trying to do here, partially because
`finally' is not used in your macro, so I think that you have some
other confusion.

> But you can't stick define-syntax in let. [...]

More specifically, the bove code is trying to define `finally' at the
wrong level -- in the macro body instead of something that is visible
to the user of the macro.  (That's also my guess based on what you
explain below.)

> The goal, for what it is worth, is to be able to write something like
>   (transaction
>     (set-account-balance (+ 10 (read-account-balance)))
>     (finally (printf "account balance updated~n")))
> or even
>   (transaction
>     (let ([new-balance (+ 10 (read-account-balance))])
>       (set-account-balance new-balance)
>       (finally (printf "new balance is: ~a~n" new-balance))))
> and have some behind-the-scenes logic rerun the transaction body as
> necessary to deal with serialization or deadlock errors, and then at
> the very end just run the finally block.

It looks like your goal is to capture `finally' unhygienicly by the
transaction macro -- a better way to do that is to use syntax
parameters, but in this case I don't think that even that is needed.
As stated above, I don't see a need for anything beyond plain Scheme

    (let ([new-balance (+ 10 (read-account-balance))])
      (set-account-balance new-balance)
      (printf "new balance is: ~a~n" new-balance))

where if `set-account-balance' throws an error, you never get to the
printout.  So I'll try to complicate things -- assuming that there is
some `capture-checkpoint' and `restore-checkpoint' functions with the
obvious meanings.  In this case, I think that this is close to what
you want:

    (let ([cp #f])
        (lambda ()
          (set! cp (capture-checkpoint)))
        (lambda ()
          (let ([new-balance (+ 10 (read-account-balance))])
            (set-account-balance new-balance)
            (set! cp #f) ; no errors => transaction successful
            (printf "new balance is: ~a~n" new-balance)))
        (lambda ()
          (when cp (restore-checkpoint cp)))))

Deriving a macro from this should be easy now.

Alternatively, you might want to have `finally' clauses appear
anywhere in the code, and that's easy to do too, here's a rough

  (define final-thunks (make-parameter #f))
  (define-syntax finally
    (syntax-rules ()
      [(finally expr ...)
       (let ([thunks (final-thunks)])
         (if thunks
           (final-thunks (cons (lambda () expr ...) thunks))
           (error 'finally "cannot be used outside of `transaction'")))]))
  (define-syntax transaction
    (syntax-rules ()
      [(transaction expr ...)
       (if (final-thunks)
         (error 'transaction "no nested transactions")
         (parameterize ([final-thunks '()])
           expr ...
           ;; get here only if there were no errors
           (for-each (lambda (t) (t)) (final-thunks))))]))

          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                  http://www.barzilay.org/                 Maze is Life!

Posted on the users mailing list.