require and syntax-case

54 views
Skip to first unread message

Roman Klochkov

unread,
Jul 12, 2020, 11:17:58 PM7/12/20
to Racket Users
I try to make a macro, that expands to the require form.

I put in test1.rkt
```
#lang racket/base
(provide ok)
(define ok 1)
```

I put in test.rkt
```
#lang racket
(define-syntax (req1 stx)
  (syntax-case stx ()
    [(_ x) #'(require x)]))

(define-syntax (req2 stx)
  (syntax-case stx ()
    [(_ (x y)) #'(require (x y))]))

(req2 (file "test1.rkt"))
ok
```

When I run it, I have the error: ok: unbound identifier

If I change `req2` to `require` or to `req1`, then it returns 1 as should.

What's wrong with (_ (x y)) pattern ?

Michael MacLeod

unread,
Jul 13, 2020, 1:17:26 AM7/13/20
to Roman Klochkov, Racket Users
The issue isn't actually with the `(_ (x y))` pattern---it's with the `#'(require (x y))` template.

When `require` is passed a module path like `(file "test1.rkt")`, it introduces any identifiers [just `ok` in this case] with the same lexical context of the module path itself[1].

The issue is that the expansion of `req2` adds a macro introduction scope to the `(file "test1.rkt")` syntax object. Since the module path has an additional scope, `ok` will have an additional scope; it won't bind the `ok` from test2.rkt, and we get an unbound identifier error.

With `req1`, the parentheses around (file "test1.rkt") in the expanded code come from the use site of the macro, so the `ok` identifier will have the expected lexical context.

With `req2`, the parentheses around (file "test1.rkt") in the expanded code come from the macro itself, not from the use site of the macro. Notice the difference: in `(require x)` the parentheses are "in the x", but in #'(require (x y)) the parentheses are from the (#') template.

You may be able to achieve your end goal using make-require-transformer[2], which would probably end up being much nicer than what I'm about to go into.

Alternatively you could resort to using the unhygenic `datum->syntax` to ensure that the resulting syntax object doesn't have the extra macro introduction scope. I've added some checks before the template to ensure that the forms passed to the macro are of the proper shape.

```
(define-syntax (req2 stx)
  (syntax-case stx ()
    [(_ (x y))
     ; throw a "bad syntax" error if the
     ; arguments are incorrect
     (and (free-identifier=? #'x #'file)
            (string? (syntax-e #'y)))
     #`(require
         #,(datum->syntax
            ; create the syntax object (x y)
            ; using the lexical context of y
            #'y
            (list #'x #'y)))]))
```

You may want to use `syntax-parse` instead, which makes these sorts of checks much simpler to write, and usually gives better error messages than "bad syntax".

```
(require (for-syntax racket/base syntax-parse))
(define-syntax (req2 stx)
  (syntax-parse stx
    [(_ ((~literal file) y:str))
     #`(require #,(datum->syntax #'y (list #'file #'y)))]))
```

---



--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/af6dcad0-69ff-406f-8183-dc691d302a5eo%40googlegroups.com.

Roman Klochkov

unread,
Jul 13, 2020, 3:50:52 AM7/13/20
to Racket Users
I tried
```
(define-syntax my-file
  (make-require-transformer
   (lambda (stx)
     (syntax-case stx ()
       [(_ path)
        (printf "Importing: ~a~n" #'path)
        (expand-import #'(file path))]))))
(require (my-file "test.rkt"))
```
with the same result: no errors in require, but no imports.

So, it seems, that the only solution is datum->syntax. It works fine.

понедельник, 13 июля 2020 г., 10:17:26 UTC+5 пользователь Michael MacLeod написал:
To unsubscribe from this group and stop receiving emails from it, send an email to racket...@googlegroups.com.

Ben Greenman

unread,
Jul 13, 2020, 8:32:58 PM7/13/20
to Racket Users
On 7/13/20, Roman Klochkov <kalime...@gmail.com> wrote:
> I tried
> ```
> (define-syntax my-file
> (make-require-transformer
> (lambda (stx)
> (syntax-case stx ()
> [(_ path)
> (printf "Importing: ~a~n" #'path)
> (expand-import #'(file path))]))))
> (require (my-file "test.rkt"))
> ```
> with the same result: no errors in require, but no imports.
>
> So, it seems, that the only solution is datum->syntax. It works fine.

There is another way: syntax-local-introduce will remove the macro scope.

```
(define-syntax (req2 stx)
(syntax-case stx ()
[(_ (x y)) (syntax-local-introduce #'(require (x y)))]))
```

Michael Ballantyne

unread,
Jul 14, 2020, 11:20:26 AM7/14/20
to Racket Users
> There is another way: syntax-local-introduce will remove the macro scope.

`syntax-local-introduce` is no longer useful for this purpose since the switch to the scope sets model. Other scopes, such as module scopes, will often distinguish the macro-introduced name. For example, using `syntax-local-introduce` won't produce the desired behavior when `req2` is defined in one module and used in another.
Reply all
Reply to author
Forward
0 new messages