[racket] module+ test... (require rackunit) results in unbound identifier when used in macro

From: Scott Klarenbach (scott at pointyhat.ca)
Date: Wed Jan 8 19:11:39 EST 2014

Murky indeed.  Here's a final note to add to my confusion.

When I *use* the macro definition you suggested above, inside the same
file, everything works.

But when I require the macro into another file and run it, now test-case is
unbound.  So I again changed the macro definition to:

(define-syntax (define/test stx)
  (syntax-parse stx
[(_ (proc:id arg:expr ...)
body:expr ...
#:test test-body:expr ...)
 (with-syntax ([id-str (symbol->string (syntax->datum #'proc))]
[ru-require (format-id stx "~a" "rackunit")])
   #'(begin
   (define (proc arg ...) body ...)
   (module+ test
 (require ru-require)

 (test-case id-str test-body ...))))]))


On Wed, Jan 8, 2014 at 4:00 PM, Carl Eastlund <carl.eastlund at gmail.com>wrote:

> On Wed, Jan 8, 2014 at 6:44 PM, Scott Klarenbach <scott at pointyhat.ca>wrote:
>
>> Thanks guys.
>>
>> I changed it to this:
>>
>> (define-syntax (define/test stx)
>>   (syntax-parse stx
>> [(_ (proc:id arg:expr ...)
>>  body:expr ...
>> #:test test-body:expr ...)
>>  (with-syntax ([id-str (symbol->string (syntax->datum #'proc))]
>>  [ru-require (format-id stx "~a" "rackunit")])
>>    #'(begin
>>    (define (proc arg ...) body ...)
>>    (module+ test
>>  (require ru-require)
>>  (test-case id-str test-body ...))))]))
>>
>> and it works now.  Is that what you meant Carl?
>>
>
> Yep, that's it.
>
>
>>  The problem is that check-true (and check-false) are not in scope (as
>>> you discovered) during expansion.
>>
>>
>> I'm still a bit confused as to why.  I figured the (require rackunit)
>> would have brought check-true into scope during expansion, much the same as
>> if I just copied the (begin... form into another file without the macro.
>>
>
> The (require rackunit) does bring the names into scope... but _which_
> scope?  That's always the important question with macros.  By default, a
> binding form brings names into the scope in which the binding occurrence of
> the names are written.  That's why if you write a local binding inside a
> macro definition, only the code inside the macro can see it, while if you
> bind a name that the user passed in, the user can see it.
>
> With require, neither party writes the name; the name shows up in the
> module definition.  Therefore, the require form has to figure out what
> scope to bind the names in some other way.  So, for module names at least,
> require uses the scope in which the module name is written to bind all of
> the definitions provided by the module.  (Things get more interesting if
> you use prefix-in or other import forms; they each have their own rules.)
>
> In your example, what scope was the module name rackunit written in?
> Inside the macro definition.  That's why writing #'(let ([check-true
> <stuff>]) body ...)  and#'(begin (require rackunit) body ...) both make
> check-true visible only inside the macro definition.  To make check-true
> visible to the macro user, you have to change the context of the name
> check-true.  In the first case, that means changing the context of the
> identifier check-true; in the second, it means changing the context of the
> module name rackunit.
>
> So when things go wrong with macro bindings, the first question is always
> "_which_ scope is this bound in?" and usually the second is "well, what
> context does the binding occurrence have?".
>
> I hope that helps, these are really murky waters, so if you're confused,
> you're definitely not alone.  We're happy to ask questions frequently and
> in detail.
>
> Good luck!
>
>
>>  On Wed, Jan 8, 2014 at 2:37 PM, Carl Eastlund <carl.eastlund at gmail.com>wrote:
>>
>>> I do not suggest rebinding the whole body like that; it could be
>>> seriously problematic if the user refers to something not viable to the
>>> macro definition.  What you need to rebind is the name "rackunit" itself
>>> passed to require so that it has the context of the macro application.
>>> That way the names bound by the require will be visible to the macro user.
>>>  On Jan 8, 2014 5:29 PM, "Stephen Chang" <stchang at ccs.neu.edu> wrote:
>>>
>>>> The problem is that check-true (and check-false) are not in scope (as
>>>> you discovered) during expansion. Here is one way to fix your problem
>>>> by recapturing the test expressions to have the proper context. You
>>>> can also use syntax-local-introduce instead of the lambda (someone
>>>> correct me if this is not proper usage).
>>>>
>>>> #lang racket
>>>> (require (for-syntax syntax/parse))
>>>>
>>>> (define-syntax (define/test stx)
>>>>   (syntax-parse stx
>>>>     [(_ (id arg ...) body ... #:test test-body ...)
>>>>      (with-syntax ([id-str (symbol->string (syntax->datum #'id))]
>>>>                    [(new-test-body ...)
>>>>                     (map
>>>>                      (λ (s) (datum->syntax #'here (syntax->datum s)))
>>>>                      (syntax->list #'(test-body ...)))])
>>>>        #'(begin
>>>>            (define (id arg ...) body ...)
>>>>            (print id-str)
>>>>            (module+ test
>>>>              (require rackunit)
>>>>              (test-case id-str
>>>>                         new-test-body ...))))]))
>>>>
>>>> (define/test (my-fn a b c)
>>>>   (print a)
>>>>   (print b)
>>>>   (print c)
>>>>   #:test
>>>>   (check-true #t)
>>>>   (check-false #t))
>>>>
>>>> On Wed, Jan 8, 2014 at 4:53 PM, Scott Klarenbach <scott at pointyhat.ca>
>>>> wrote:
>>>> > I have the following macro:
>>>> >
>>>> > (define-syntax (define/test stx)
>>>> >   (syntax-parse stx
>>>> > [(_ (id arg ...) body ... #:test test-body ...)
>>>> > (with-syntax ([id-str (symbol->string (syntax->datum #'id))])
>>>> >   #'(begin
>>>> >   (define (id arg ...) body ...)
>>>> >   (print id-str)
>>>> >   (module+ test
>>>> > (require rackunit)
>>>> > (test-case id-str
>>>> >   test-body ...))))]))
>>>> >
>>>> > Which handles some testing boilerplate and allows me to use it like
>>>> so:
>>>> >
>>>> > (define/test (my-fn a b c)
>>>> >   (print a)
>>>> >   (print b)
>>>> >   (print c)
>>>> >   #:test
>>>> >   (check-true #t)
>>>> >   (check-false #t))
>>>> >
>>>> > The problem is that the (require rackunit) expression inside of
>>>> (module+
>>>> > test) is not being picked up after macro expansion.  Running either
>>>> the code
>>>> > or the tests (via raco test) results in: unbound identifier
>>>> check-true.
>>>> >
>>>> > Adding (module+ (require rackunit)) to the top of the file solves the
>>>> > problem and both the file and tests run as expected.
>>>> >
>>>> > What am I missing?
>>>> >
>>>> > Thanks.
>>>> > --
>>>> > Talk to you soon,
>>>> >
>>>> > Scott Klarenbach
>>>> >
>>>> > PointyHat Software Corp.
>>>> > www.pointyhat.ca
>>>> > p 604-568-4280
>>>> > e scott at pointyhat.ca
>>>> > 200-1575 W. Georgia
>>>> > Vancouver, BC V6G2V3
>>>> >
>>>> > _______________________________________
>>>> > To iterate is human; to recur, divine
>>>> >
>>>> > ____________________
>>>> >   Racket Users list:
>>>> >   http://lists.racket-lang.org/users
>>>> >
>>>>
>>>> ____________________
>>>>   Racket Users list:
>>>>   http://lists.racket-lang.org/users
>>>>
>>>
>>
>>
>> --
>> Talk to you soon,
>>
>> Scott Klarenbach
>>
>> PointyHat Software Corp.
>> www.pointyhat.ca
>> p 604-568-4280
>> e scott at pointyhat.ca
>> 200-1575 W. Georgia
>> Vancouver, BC V6G2V3
>>
>> _______________________________________
>> To iterate is human; to recur, divine
>>
>
>


-- 
Talk to you soon,

Scott Klarenbach

PointyHat Software Corp.
www.pointyhat.ca
p 604-568-4280
e scott at pointyhat.ca
200-1575 W. Georgia
Vancouver, BC V6G2V3

_______________________________________
To iterate is human; to recur, divine
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20140108/2eee5449/attachment-0001.html>

Posted on the users mailing list.