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

From: Carl Eastlund (carl.eastlund at gmail.com)
Date: Thu Jan 9 00:40:45 EST 2014

Ah, good point.  If that's a concern then you want to do it your way.  My
way would depend on rackunit when running the main module.

Carl Eastlund

On Wed, Jan 8, 2014 at 7:36 PM, Scott Klarenbach <scott at pointyhat.ca> wrote:

> Would that affect the loading of rackunit during main vs test runs of the
> file?  For example,
> >> racket file-that-uses-macro.rkt
> >> raco test file-that-uses-macro.rkt
>
> ;; will the *non-test *invocation now require rackunit due to the macro
> definition, where as the dual require inside of module+ would not?
>
> Thanks.
>
>
> On Wed, Jan 8, 2014 at 4:29 PM, Carl Eastlund <carl.eastlund at gmail.com>wrote:
>
>> That's a good solution.  I can suggest one that saves you having to add
>> two requires each time you use the macro, although it's a small
>> optimization: simply add (require rackunit) to the top of the file where
>> you define the macro, entirely outside the macro definition.  Then you
>> don't need the extra (require rackunit) inside the macro template; you'll
>> be able to refer to test-case from rackunit the same way you referred to
>> require and provide from racket.
>>
>> Carl Eastlund
>>
>>
>> On Wed, Jan 8, 2014 at 7:13 PM, Scott Klarenbach <scott at pointyhat.ca>wrote:
>>
>>> Sorry, gmail sent before I could paste :(
>>>
>>> ...
>>>
>>> So I changed the macro definition to:
>>>
>>> (define-syntax (define/spec stx)
>>>   (syntax-parse stx
>>>  [(_ (proc:id arg:expr ...)
>>> body:expr ...
>>> #:doc contract:expr arg-spec:expr desc:expr
>>>  #:test test-body:expr ...)
>>>  (with-syntax ([id-str (symbol->string (syntax->datum #'proc))]
>>>    [ru-require (format-id stx "~a" "rackunit")])
>>>    #'(begin
>>>    (provide
>>>  (proc-doc/names proc contract arg-spec desc))
>>>    (define (proc arg ...) body ...)
>>>    (module+ test
>>>  (require ru-require)
>>>  (require rackunit)
>>>  (test-case id-str test-body ...))))]))
>>>
>>> Notice the extra (require rackunit), after the modified (require
>>> ru-require).  Is that the right solution?
>>>
>>>
>>> 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
>>>
>>
>>
>
>
> --
> 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/20140109/a021c564/attachment-0001.html>

Posted on the users mailing list.