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.