Hmm, no takers on this wall of text? ;)

Turns out I was looking in the wrong direction. While reading some of Matthew's 
sets of scopes notes, I stumbled across an example with exactly the tool I 
needed: `syntax-local-introduce'. Each of the intermediary `let-syntax'-bound 
macros just needed its identifier introduced in-context.

A more minimal example of the working code is below. Now that this is solved, 
is there a better way? Stacking units up was a bit too brittle in my pre-macro 
attempts.

#lang racket
(require (for-syntax syntax/parse racket/syntax)
         racket/splicing)

(define-syntax-rule (save-bind name id val)
  (define-syntax name (syntax [id val])))

(save-bind FOO foo 1)
(splicing-let ([bar-local 2])
  (save-bind BAR bar (+ bar-local foo)))
(splicing-let ([baz-local 3])
  (save-bind BAZ baz (+ baz-local bar)))

(define-syntax (dyn-bind stx)
  (syntax-parse stx
    
    [(dyn-bind before ... name:id after ...)
     #`(dyn-bind before ... #,(syntax-local-value #'name) after ...)]

    [(dyn-bind [id:id val:expr] after ...+)
     #`(splicing-let-syntax
           ([#,(syntax-local-introduce (datum->syntax #f (syntax->datum #'id)))
             (make-set!-transformer
              (λ (stx) (syntax-case stx ()
                         [id (identifier? #'id) #'val])))])
         (dyn-bind after ...))]

    [(dyn-bind [id:id val:expr])
     #`(define-syntax #,(format-id #'dyn-bind "~a" (syntax-e #'id))
         (make-set!-transformer
          (λ (stx) (syntax-case stx ()
                     [id (identifier? #'id) #'val]))))]))

(splicing-let ([bar-local 500]
               [baz-local 1000])
  (dyn-bind FOO BAR BAZ))

(equal? baz 6)

Nick

On Monday, April 25, 2016 at 6:18:41 PM UTC-4, Nicholas Labich wrote:
> 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 [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to