[racket] Advice how to refactor code to share pattern variables but split out templates?

From: Greg Hendershott (greghendershott at gmail.com)
Date: Thu Sep 5 17:45:23 EDT 2013

Ah, from re-RTFM-ing the syntax-parse docs, looks like I've
encountered the first situation where I would need to use `(require
(for-template))`. Always wondered what that was for.

<blockquote>
If a syntax class refers to literal identifiers, or if it computes
expressions via syntax templates, then the module containing the
syntax class must generally require for-template the bindings referred
to in the patterns and templates.
</blockquote>

http://docs.racket-lang.org/syntax/Phases_and_Reusable_Syntax_Classes.html

On Thu, Sep 5, 2013 at 2:46 PM, Greg Hendershott
<greghendershott at gmail.com> wrote:
> Hmm but I'm stuck on how to split this across multiple files.
>
> I want to move the syntax-class definitions to a new "core.rkt" and
> keep the wrappers in "main.rkt".
>
> Because main.rkt will `(require (for-syntax "core.rkt"))`, I make
> core.rkt consist of plain phase 0 stuff:
>
> #lang racket/base
> (require syntax/parse  ;; NOT require for-syntax
>          racket/syntax
>          racket/string
>          racket/format)
> (provide def)
> (define-syntax-class def ....)  ;; NOT in begin-for-syntax
>
> And in main.rkt for instance:
>
> #lang racket
> (require (for-syntax syntax/parse
>                      racket/syntax))
> (require (for-syntax "core.rkt"))
> (define-syntax (defn stx)
>   (syntax-parse stx
>     [d:def #`(begin
>                (provide (contract-out [d.ID d.CONTRACT]))
>                (define d.SIG d.BODY ...)
>                d.TEST
>                d.DOC )]))
>
> However, I get errors that identifiers are undefined. For example:
>
> (defn (foo [x number?] -> number?)
>   (* 2 x))
> ; core.rkt:54:11: module+: unbound identifier;
> ; also, no #%app syntax transformer is bound
> ;  at: module+
> ;  in: (module+ test (require rackunit))
>
> Now, if I comment out all but the basic define, then no errors:
>
> (define-syntax (defn stx)
>   (syntax-parse stx
>     [d:def #`(begin
>                ;;;;;(provide (contract-out [d.ID d.CONTRACT]))
>                (define d.SIG d.BODY ...)
>                ;;;;;d.TEST
>                ;;;;;d.DOC
>                )]))
>
> It seems the lexical context is getting "sliced off", similar to when
> misusing (datum->syntax #f (syntax->datum ....). But I thought the
> syntax-class #:with items would be preserving the original stx context
> given to syntax-parse?  It worked when they were in the same file.
>
> Also, why are the syntax attributes SIG and BODY fine, but not all the
> rest? They solely rearrange original syntax. Whereas the other
> introduce symbols like `module+`.  That seems like an important clue,
> but I can't quite cash it in for the solution.
>
>
> On Thu, Sep 5, 2013 at 11:47 AM, Greg Hendershott
> <greghendershott at gmail.com> wrote:
>> Oh, wow, that is elegant.
>>
>> Before seeing your post, I'd worked ahead on my own and came up with
>> something where the core helper function calls back to the
>> wrapper-provided higher-order function with a #'(LIST OF PATTEN VARS)
>> --- which the callee needs to syntax-case back into pattern variables
>> again:
>>
>> https://github.com/greghendershott/def-jambda/commit/eda5bf8ce5668e49ba4068c50eab8055e34fcdc7
>>
>> Although it works, it feels like using a list instead of a struct, and
>> using (match-define (list ....) x) instead of using struct field
>> accessors. With similar pros/cons. A con: It's fragile. If the list of
>> pattern variables were to change someday, it would break in a
>> less-helpful way. (Changing struct fields would still break, but more
>> helpfully. Adding a new field wouldn't break, at all.)
>>
>> And so IIUC a syntax-class is the analog of a struct, for this.
>>
>> Which was probably spelled out plainly on slide 2 of a presentation I
>> saw Ryan give years ago. O_o  But if so, at the time I only took away
>> the sense in which syntax-parse improves parsing details of
>> syntax-case.  I guess this is one of those cases where I had to try
>> the more-painful way myself, to be ready to learn/appreciate the
>> better way.
>>
>> Cool!
>>
>>
>>
>> On Wed, Sep 4, 2013 at 11:03 PM, Stephen Chang <stchang at ccs.neu.edu> wrote:
>>> Could you throw all the common parts into a syntax-class?
>>>
>>> That would enable you to put the cond branches in the defns
>>> themselves, which I agree is better.
>>>
>>> Something like this: https://gist.github.com/stchang/6445553
>>>
>>> I havent tested extensively but it seems to behave the same as your
>>> code on a few small examples.
>>>
>>> On Wed, Sep 4, 2013 at 8:37 PM, Greg Hendershott
>>> <greghendershott at gmail.com> wrote:
>>>> Given some macros that are wrappers around a core helper function:
>>>>
>>>> (define-syntax (wrapper1 stx)
>>>>   (core #f #f))
>>>> (define-syntax (wrapper2 stx)
>>>>   (core #f #t))
>>>> (define-syntax (wrapper3 stx)
>>>>   (core #t #f))
>>>> (define-syntax (wrapper4 stx)
>>>>   (core #t #t))
>>>>
>>>> (define-for-syntax (core stx opt1? opt2?)
>>>>   (define-syntax-class x ....)
>>>>   (syntax-parse stx
>>>>     [(_ pattern )
>>>>      (with-syntax*
>>>>        ([ <bunch of pattern variables> ])
>>>>        ;; Ugly cond using opt1? and opt2
>>>>        (cond [opt1? (cond [opt2? #'( <use ptn vars> )]
>>>>                           [else #'( <use ptn vars> )])]
>>>>              [else (cond [opt2? #'( <use ptn vars> )]
>>>>                          [else #'( <use ptn vars> )])]))]))
>>>>
>>>> Now I want/need to refactor the code. For one thing, some of the
>>>> wrappers must be in a file with `#lang racket`, and others in another
>>>> file with `#lang typed/racket`. Plus, the cond stuff is ugly; each
>>>> wrapper should handle this itself.  And in refactoring, of course I
>>>> want to D.R.Y. instead of copy pasta.
>>>>
>>>> The following seems somewhat better. Have the wrappers supply a
>>>> "callback" function to the core:
>>>>
>>>> ;; wrap12.rkt
>>>> #lang racket
>>>> (require "core.rkt")
>>>> (define-syntax (wrapper1 stx)
>>>>   (core (lambda (stx <bunch of pattern vars> )
>>>>           #'( <use ptn vars> ))))
>>>> .. wrapper2 similar ..
>>>>
>>>> ;; wrap34.rkt
>>>> #lang typed/racket
>>>> (require "core.rkt")
>>>> ... wrappers 3 4 similar ..
>>>>
>>>> ;; core.rkt
>>>> (provide core)
>>>> (define-for-syntax (core stx f)
>>>>   (define-syntax-class y ....)
>>>>   (syntax-parse stx
>>>>     [(_ pattern )
>>>>      (with-syntax*
>>>>        ([ <bunch of pattern variables> ])
>>>>        (f stx <bunch of pattern variables> ))])) ;; give back to the
>>>> caller and let it make the syntax
>>>>
>>>> And now it's easy for them to be in different files. However:
>>>>
>>>> 1. Now instead the ugly part is schlepping the pattern variables
>>>> around (e.g. half dozen function parameters).
>>>>
>>>> 2. Pattern variables and templates can't be separated like this and
>>>> still work, AFAICT.
>>>>
>>>> I have a few ideas that I'll go ahead and try, but I wanted to go
>>>> ahead and post this to see what ideas or advice anyone might have.
>>>>
>>>>
>>>> p.s. In case my question and code sketch here is too abstract, my
>>>> motivating full example is this:
>>>>
>>>> https://github.com/greghendershott/def-jambda/blob/50f6abcb7a64558756dce653c1faac4c012d4167/main.rkt
>>>> ____________________
>>>>   Racket Users list:
>>>>   http://lists.racket-lang.org/users

Posted on the users mailing list.