[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:36:24 EST 2014

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/20140108/98e2fe60/attachment-0001.html>

Posted on the users mailing list.