[racket] module+ test... (require rackunit) results in unbound identifier when used in macro
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>