[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 14:46:55 EDT 2013

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.