Thanks for the feedback. Attached is the solution applied to my original
implementation (only 65loc, documented). A couple of interesting points:
- My simplified example didn't show this, but I have to
`syntax-local-introduce' the identifiers recursively, else
references to symbol=? `let-syntax'-bound identifiers are
ambiguous. Each `returnₘ' reference can have at most one other
`returnₘ' identifier in scope.
- For the `syntax-local-introduce'd identifiers to be visible in the
body of the `splicing-let-syntax' [line 66], recursive calls to
`use-monad' have to be `local-expand'ed by hand. I suppose I could
recur over the stack from the top down, but I would need to
special-case the first iteration after MId deference, which seems
sloppier.
If there are any fixes for this second point, I'd be interested to hear. For
now, this should be a good starting point.
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