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.
def-monad.rkt
Description: Binary data

