我正在尝试制作一个小shell,用于在csv文件上执行类似sql的查询(出于好奇,也是为了尝试学习球拍)。为此,我想用这个粗略的结构实现一个select宏(我计划让x是数据库的列,但现在只传递了一行):
(define-syntax select
(syntax-rules (* from where)
((_ col1 ... from db where condition)
(filter (lambda (x) condition) <minutiae>))))(其中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示例无效?
发布于 2019-04-29 16:28:17
正如你所猜到的,这个问题是关于卫生的。
let示例之所以有效,是因为在给定体中使用的标识符被传递给宏。
但是,如果您试图在主体中定义x标识符,而没有让编写主体的人真正明确地了解它,那么您就违反了规范(在作用域中插入任意绑定)。
您要创建的宏称为anaphoric宏。幸运的是,球拍有你需要的东西。
语法参数
如果你以前使用过球拍参数,它的工作原理是一样的,除了宏。
(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))发布于 2019-04-29 16:28:30
这是可行的:
(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))有一件事可能有助于理解其中的区别,那就是can可以随心所欲地重命名变量,只要重命名是一致的(实际上很难在这里定义一致这个词,但直觉上它对您来说应该是有意义的)。球拍需要能够重命名以保持卫生。
在第一种情况下,调用(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是未绑定的。
(请注意,我把事情简化了很多。实际上,在第一种情况下,球拍需要展开宏,以便知道它需要同时重命名这两个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和下面的评论,了解另一种打破卫生习惯的方法。
https://stackoverflow.com/questions/55897774
复制相似问题