I'm trying to write a small monad transformer library with macros.

My goal:

  (define-monad IdM
    [(return a) a]
    [(bind m f) (f m)]
    [(foo)      'id-foo])

  (define-trans ListT
    [(return a) (returnₘ (list a))]
    [(bind m f) (bindₘ m (λ (loa) (foldl (λ (a m′) (returnₘ (append (f a) m′)))
                                         '()
                                         loa)))]
    [(foo)      (cons 'list-foo (fooₘ))])

  (let ([ladd1 (λ (n) (list (add1 n)))])
    (use-monad ListT IdM)
    (displayln (foo))
    (equal? (bind (return 0) ladd1) (list 1)))
  ; =>
  ; (list-foo . id-foo)
  ; #t

The problem:

I want to delay binding of returnₘ, bindₘ, and fooₘ (in general,
arbitrary lifted effects) until `use-monad' expansion. I also want to
save as much lexical context from the define-{monad,trans} sites as
possible. Each `identₘ' should refer to the
`splicing-let-syntax'-bound macros of the monad directly below that
transformer in the stack during `use-monad' expansion (see below for
an example expanded result).

My implementation:

Start at the bottom (right side) of the stack given to `use-monad',
recursively generating `splicing-let-syntax' bindings for the
ops/effects of each layer of the transformer stack. When at the top,
generate `define-syntax' bindings with identifiers `format-id'ed to
be exposed at the `use-monad' call-site.

The attached file has the whole implementation, but the gist of it is
this (assuming the above definitions of ListT and IdM):

  (use-monad ListT IdM)
  ; =>
  (splicing-let-syntax
      ([returnₘ (syntax-id-rules ()
                  [(returnₘ a) a]
                  [returnₘ (λ (a) a)])]
       [bindₘ   (syntax-id-rules ()
                  [(bindₘ m f) (f m)]
                  [bindₘ (λ (m f) (f m))])]
       [fooₘ    (syntax-id-rules ()
                  [(fooₘ) 'id-foo]
                  [fooₘ (λ () 'id-foo)])])
    (begin
      (define-syntax return
        (syntax-id-rules ()
          [(return a) (returnₘ (list a))]
          [return (λ (a) (returnₘ (list a)))]))
      (define-syntax bind
        (syntax-id-rules ()
          [(bind m f)
           (bindₘ m (λ (loa) (foldl (λ (a m′) (returnₘ (append (f a) m′)))
                                    '() loa)))]
          [bind
           (λ (m f)
             (bindₘ m (λ (loa) (foldl (λ (a m′) (returnₘ (append (f a) m′)))
                                      '() loa))))]))
      (define-syntax foo
        (syntax-id-rules ()
          [(foo) (cons 'list-foo (fooₘ))]
          [foo   (λ () (cons 'list-foo (fooₘ)))]))))

As implemented, all of the `identₘ'-form identifiers are unbound when
`return', `bind', and `foo' are called.

Possible/attempted solutions:

If I take the entire result of `use-monad' and fully replace its
context:

  (datum->syntax stx (syntax->datum (syntax-parse stx ...)))

the test "succeeds", but only by throwing away all context from the
define-{monad,trans} sites. Is there a way to add macro bindings for
the constructed `identₘ' transformers to the local context of the
final definitions without throwing away the rest (or just expand them
away during `use-monad's expansion)?

I've spent a few days trying to work with the `local-expand' variants,
but have yet to find the proper invocation/ritual-sacrifice to expand
the macros as I construct them in `use-monad' (the documentation is
nigh-impenetrable, at least to this uninitiated).

I don't think syntax-parameters are the right solution, because while
the effects that need to be lifted (and bound as `identₘ') will all
be known statically, name collisions would prevent definitions at
`define-trans' sites (since all transformers would define `returnₘ'
and `bindₘ' parameters). If syntax parameters are to be defined and
`(let () ...)'-guarded at each `use-monad' site, it wouldn't fix
the unbound definitions from `define-trans' sites (or would it?).

What's the most hygienic solution that makes this dirty macro work?
Any pointer in the right direction would be appreciated.

Cheers,
Nick

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Attachment: def-monad.rkt
Description: Binary data

Reply via email to