Как вы уже догадались, проблема касается гигиены.
Пример let работает, потому что идентификаторы, используемые внутри данного тела, передаются макросу.
Но если вы попытаетесь определитьx
идентификатор внутри тела, без того, чтобы человек, пишущий тело, на самом деле точно знал об этом , тогда вы нарушаете гигиену (вставляя произвольную привязку в область действия).
Чтокоторый вы хотите создать, называется макросом anaphoric .К счастью, у Racket есть то, что вам нужно для создания.
Синтаксический параметр
Если вы когда-либо использовали параметры Racket раньше, он работает немного так же, но для макросов.
(define-syntax-parameter <x>
(lambda (stx)
(raise-syntax-error '<x> "Used outside select macro." stx)))
Это определит параметр с именем <x>
, который пользователи макроса смогут использовать внутри макроса select
.Чтобы предотвратить его использование снаружи, по умолчанию этот параметр настроен на выдачу синтаксической ошибки.
Чтобы определить единственное место, в котором его можно использовать, вы вызываете syntax-parameterize
:
(define-syntax select
(syntax-rules (* from where)
[(_ col1 ... from db where condition)
(findf
(lambda (x)
(syntax-parameterize ([<x> (make-rename-transformer #'x)])
condition))
<minutiae>)]))
Это создаст новую область видимости вокруг condition
, в которой <x>
привязан к x
в лямбде.
Затем вы можете вызвать свой макрос следующим образом:
(select * from db where (eq? <x> 'foo))
Если вы попытаетесь использовать <x>
вне макроса, вы получите синтаксическую ошибку:
> (displayln <x>)
<x>: Used outside select macro.
in: <x>
Полный код
#lang racket/base
(require
(for-syntax racket/base)
racket/stxparam)
(define-syntax-parameter <x>
(lambda (stx)
(raise-syntax-error '<x> "Used outside select macro." stx)))
(define-syntax select
(syntax-rules (* from where)
[(_ col1 ... from db where condition)
(findf
(lambda (x)
(syntax-parameterize ([<x> (make-rename-transformer #'x)])
condition))
db)]))
(module+ test
(require rackunit)
(define db '(foo bar baz))
(check-equal? (select * from db where (eq? <x> 'foo)) 'foo)
(check-equal? (select * from db where (eq? <x> 'bar)) 'bar)
(check-equal? (select * from db where (eq? <x> 'boop)) #f))