我正在尝试制作一个小外壳,用于对csv文件执行类似sql的查询(出于好奇并尝试学习Racket)。为此,我想用这种粗略的结构实现一个select宏(我计划将x作为数据库的列,但现在只传递了一行):
(define-syntax select
(syntax-rules (* from where)
((_ col1 ... from db where condition)
(filter (lambda (x) condition) <minutiae>))))
(其中细节是文件IO和管道代码)
x的范围不是我想的那样:
x: undefined;
cannot reference an identifier before its definition
我发现了this example of a let-like macro:
(define-syntax my-let*
(syntax-rules ()
((_ ((binding expression) ...) body ...)
(let ()
(define binding expression) ...
body ...))))
然后我继续尝试像这样生成lambda:
(define-syntax my-lambda
(syntax-rules ()
((_ body)
(lambda (x)
body))))
然后尝试模仿let示例的结构:
(define-syntax my-lambda
(syntax-rules ()
((_ body)
(lambda (x_)
(let ()
(define x x_)
body)))))
这两个在调用((my-lambda (+ x 1)) 0)
时都给了我同样的错误:
x: undefined;
cannot reference an identifier before its definition
根据我的阅读,这是出于卫生原因,但是我似乎不太了解它,无法靠自己解决。我在做什么错?如何定义这些宏?为什么让let示例起作用,而不是lambda示例?
答案 0 :(得分:3)
就像您猜到的那样,问题出在卫生上。
let示例之所以起作用,是因为给定主体内使用的标识符将传递给宏。
但是,如果您尝试在体内定义x
标识符,而又没有让人们真正地明确地了解,那么您正在破坏卫生习惯(插入任意绑定在范围内)。
您要创建的内容称为 anaphoric 宏。 幸运的是,球拍有您需要做的一个。
如果您以前曾使用过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>
绑定到lambda中的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))
答案 1 :(得分:0)
这有效:
(define-syntax my-lambda
(syntax-rules ()
((_ x body)
(lambda (x) body))))
(my-lambda x (+ x 1))
这不起作用:
(define-syntax my-lambda*
(syntax-rules ()
((_ body)
(lambda (x) body))))
(my-lambda* (+ x 1))
一件事可能有助于理解这种差异,那就是Racket可以重命名变量,但是只要重命名是一致的(实际上很难在此处定义一致的单词,但是从直觉上讲,这对您来说就很有意义了)。球拍需要能够重命名以保持卫生。
在第一种情况下,您致电(my-lambda x (+ x 1))
。球拍可能会将其重命名为(my-lambda x$0 (+ x$0 1))
。扩展宏,我们得到(lambda (x$0) (+ x$0 1))
。
在第二种情况下,您致电(my-lambda* (+ x 1))
。球拍可能会将其重命名为(my-lambda* (+ x$0 1))
。扩展宏,我们得到(lambda (x) (+ x$0 1))
,因此x$0
是未绑定的。
(请注意,我在简化很多事情。实际上,Racket在第一种情况下需要扩展宏,以知道它需要同时重命名两个x
。)
要获得所需的食物,您需要打破卫生习惯。这是一种简单的方法:
(require syntax/parse/define)
(define-simple-macro (my-lambda** body)
#:with unhygiene-x (datum->syntax this-syntax 'x)
(lambda (unhygiene-x) body))
(my-lambda** (+ x 1))
另请参阅https://stackoverflow.com/a/55899542/718349和下面的评论,以了解另一种破坏卫生的方法。