首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >创建一个生成lambda的球拍宏

创建一个生成lambda的球拍宏
EN

Stack Overflow用户
提问于 2019-04-29 14:04:22
回答 2查看 256关注 0票数 1

我正在尝试制作一个小shell,用于在csv文件上执行类似sql的查询(出于好奇,也是为了尝试学习球拍)。为此,我想用这个粗略的结构实现一个select宏(我计划让x是数据库的列,但现在只传递了一行):

代码语言:javascript
复制
(define-syntax select
  (syntax-rules (* from where)
    ((_ col1 ... from db where condition)
     (filter (lambda (x) condition) <minutiae>))))

(其中minutiae是文件IO和管道代码)

X的作用域并不是我想的那样:

代码语言:javascript
复制
x: undefined;
 cannot reference an identifier before its definition

我找到了this example of a let-like macro

代码语言:javascript
复制
(define-syntax my-let*
  (syntax-rules ()
    ((_ ((binding expression) ...) body ...)
     (let ()
       (define binding expression) ...
       body ...))))

然后我继续尝试像这样产生lambda:

代码语言:javascript
复制
(define-syntax my-lambda
  (syntax-rules ()
    ((_ body)
     (lambda (x)
       body))))

然后尝试模仿let示例的结构:

代码语言:javascript
复制
(define-syntax my-lambda
  (syntax-rules ()
    ((_ body)
     (lambda (x_)
       (let ()
         (define x x_)
         body)))))

在调用((my-lambda (+ x 1)) 0)时,这两个命令都给了我相同的错误

代码语言:javascript
复制
x: undefined;
 cannot reference an identifier before its definition

根据我的阅读,这是由于卫生问题,但我似乎不能很好地掌握它,以解决这个问题。我做错了什么?如何定义这些类型的宏?为什么let示例有效,而lambda示例无效?

EN

回答 2

Stack Overflow用户

发布于 2019-04-29 16:28:17

正如你所猜到的,这个问题是关于卫生的。

let示例之所以有效,是因为在给定体中使用的标识符被传递给宏。

但是,如果您试图在主体中定义x标识符,而没有让编写主体的人真正明确地了解它,那么您就违反了规范(在作用域中插入任意绑定)。

您要创建的宏称为anaphoric宏。幸运的是,球拍有你需要的东西。

语法参数

如果你以前使用过球拍参数,它的工作原理是一样的,除了宏。

代码语言:javascript
复制
(define-syntax-parameter <x>
  (lambda (stx)
    (raise-syntax-error '<x> "Used outside select macro." stx)))

这将定义一个名为<x>的参数,宏用户将能够在select宏中使用该参数。为了防止在外部使用它,默认情况下,该参数被配置为引发语法错误。

要定义可以使用它的唯一位置,可以调用syntax-parameterize

代码语言:javascript
复制
(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

然后你可以像这样调用你的宏:

代码语言:javascript
复制
(select * from db where (eq? <x> 'foo))

如果你尝试在宏外使用<x>,你会得到一个语法错误:

代码语言:javascript
复制
> (displayln <x>)
<x>: Used outside select macro.
  in: <x>

完整代码

代码语言:javascript
复制
#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))
票数 4
EN

Stack Overflow用户

发布于 2019-04-29 16:28:30

这是可行的:

代码语言:javascript
复制
(define-syntax my-lambda
  (syntax-rules ()
    ((_ x body)
     (lambda (x) body))))

(my-lambda x (+ x 1))

虽然这不起作用:

代码语言:javascript
复制
(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。)

为了得到你想要的东西,你需要打破卫生习惯。这里有一种简单的方法:

代码语言:javascript
复制
(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和下面的评论,了解另一种打破卫生习惯的方法。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55897774

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档