使用球拍宏动态定义符号和宏
Dynamically define symbols and macros using a racket macro
我正在用 Racket 创建 DSL(领域特定语言)。在此 DSL 中,访问任何值都会导致它发生变异。换句话说,没有什么是纯粹的。我试图了解如何使用单个球拍宏来定义几个新符号和宏,以尝试减少重复代码。这个宏的一部分要求我引用一个 private/hidden 值。例如。当我引用 a
它实际上是一个宏,它调用 getter get-val
作为私有值 a_
(类似于普通 lisp 中的符号宏)。我试图遵循使用宏 here 生成宏的文档,但它似乎并没有真正涵盖如何定义一个名为 a_
的符号给定一个名为 a
的符号,因为它是有点切线。深入研究 SO 也没有给我带来太多帮助,尽管我不确定我正在寻找的功能叫什么。
每个变量都使用相同的 getter 宏来改变过程中的值。这很好用。
(define-syntax-rule (get-val x)
(begin (set! x (not x)) x))
在原始代码中,我需要像这样为每个符号手动定义一个 getter 宏。这也可以正常工作,但会导致大量重复代码。
(define a_ #f)
(define-syntax (a stx) #'(get-val a_))
(define b_ #f)
(define-syntax (b stx) #'(get-val b_))
我宁愿定义一次主宏,看起来像这样。 (不工作)。我相信 string->symbol
可能是问题所在。
(define-syntax-rule (def name val)
(begin
; Create new varname with name followed by underscore
; This should probably be a `let` ?
(define name_ (string-append name "_"))
; Assign the value to that (private) symbol.
(define (string->symbol name_) val)
; Create public getter macro
(define-syntax (name stx) #'(get-val (string->symbol name_)))))
然后我可以像这样简单地创建很多值
(def 'a #f)
(def 'b #f)
; or ideally this (not quoted)
(def a #f)
(def b #f)
一旦它起作用,我还将创建一个 peek
宏,它允许我检查值而无需为了调试目的而改变,尽管我可能会在知道如何创建第一个宏后自行解决这个问题.
我设法使用 namespace-anchors
和 eval
拼凑出一个有效的解决方案。我想可能有更好的方法来做到这一点。这是功能代码。
(define-namespace-anchor anc)
(define ns (namespace-anchor->namespace anc))
(define-syntax-rule (get-val x)
(begin (set! x (not x)) x))
(define-syntax-rule (def name val)
(begin
(define name_ (string-append (symbol->string name) "_"))
(eval `(define ,(string->symbol name_) ,val)
ns)
(eval `(define-syntax
(,name stx)
#'(get-val ,(string->symbol name_)))
ns)))
(def 'a #f)
(def 'b #f)
Racket 称它们为 "identifier macros"。实际上,Racket 宏扩展器调用宏的转换器,无论它是“在操作员位置”还是“像变量”一样使用;不同之处在于转换器(通常使用 syntax-rules
、syntax-case
或 syntax-parse
实现)接受的使用模式。
您需要像 a_
这样的私有变量才能访问吗?如果没有,您可以只使用一个常量名称,如 tmp
,和 hygiene will distinguish the tmp
defined by (def a #f)
from the tmp
defined by (def b #f)
. Then you can use make-variable-like-transformer
将定义名称的类似变量的使用转换为获取和更新值的表达式,如下所示:
(require (for-syntax racket/base syntax/transformer))
(define-syntax-rule (def name val)
(begin
(define tmp val)
(define-syntax name
(make-variable-like-transformer
#'(begin0 tmp (set! tmp (not tmp)))))))
(本版本使用begin0
获取更新前的值,当然你也可以改回来。)
如果您确实希望 a_
变量可访问,则可以使用 format-id
创建名称,但不能再使用 define-syntax-rule
来创建名称。
(require (for-syntax racket/base racket/syntax syntax/transformer))
(define-syntax (def stx)
(syntax-case stx ()
[(def name val)
(with-syntax ([name_ (format-id #'name "~a_" #'name)])
#'(begin
(define name_ val)
(define-syntax name
(make-variable-like-transformer
#'(begin0 name_ (set! name_ (not name_)))))))]))
我正在用 Racket 创建 DSL(领域特定语言)。在此 DSL 中,访问任何值都会导致它发生变异。换句话说,没有什么是纯粹的。我试图了解如何使用单个球拍宏来定义几个新符号和宏,以尝试减少重复代码。这个宏的一部分要求我引用一个 private/hidden 值。例如。当我引用 a
它实际上是一个宏,它调用 getter get-val
作为私有值 a_
(类似于普通 lisp 中的符号宏)。我试图遵循使用宏 here 生成宏的文档,但它似乎并没有真正涵盖如何定义一个名为 a_
的符号给定一个名为 a
的符号,因为它是有点切线。深入研究 SO 也没有给我带来太多帮助,尽管我不确定我正在寻找的功能叫什么。
每个变量都使用相同的 getter 宏来改变过程中的值。这很好用。
(define-syntax-rule (get-val x)
(begin (set! x (not x)) x))
在原始代码中,我需要像这样为每个符号手动定义一个 getter 宏。这也可以正常工作,但会导致大量重复代码。
(define a_ #f)
(define-syntax (a stx) #'(get-val a_))
(define b_ #f)
(define-syntax (b stx) #'(get-val b_))
我宁愿定义一次主宏,看起来像这样。 (不工作)。我相信 string->symbol
可能是问题所在。
(define-syntax-rule (def name val)
(begin
; Create new varname with name followed by underscore
; This should probably be a `let` ?
(define name_ (string-append name "_"))
; Assign the value to that (private) symbol.
(define (string->symbol name_) val)
; Create public getter macro
(define-syntax (name stx) #'(get-val (string->symbol name_)))))
然后我可以像这样简单地创建很多值
(def 'a #f)
(def 'b #f)
; or ideally this (not quoted)
(def a #f)
(def b #f)
一旦它起作用,我还将创建一个 peek
宏,它允许我检查值而无需为了调试目的而改变,尽管我可能会在知道如何创建第一个宏后自行解决这个问题.
我设法使用 namespace-anchors
和 eval
拼凑出一个有效的解决方案。我想可能有更好的方法来做到这一点。这是功能代码。
(define-namespace-anchor anc)
(define ns (namespace-anchor->namespace anc))
(define-syntax-rule (get-val x)
(begin (set! x (not x)) x))
(define-syntax-rule (def name val)
(begin
(define name_ (string-append (symbol->string name) "_"))
(eval `(define ,(string->symbol name_) ,val)
ns)
(eval `(define-syntax
(,name stx)
#'(get-val ,(string->symbol name_)))
ns)))
(def 'a #f)
(def 'b #f)
Racket 称它们为 "identifier macros"。实际上,Racket 宏扩展器调用宏的转换器,无论它是“在操作员位置”还是“像变量”一样使用;不同之处在于转换器(通常使用 syntax-rules
、syntax-case
或 syntax-parse
实现)接受的使用模式。
您需要像 a_
这样的私有变量才能访问吗?如果没有,您可以只使用一个常量名称,如 tmp
,和 hygiene will distinguish the tmp
defined by (def a #f)
from the tmp
defined by (def b #f)
. Then you can use make-variable-like-transformer
将定义名称的类似变量的使用转换为获取和更新值的表达式,如下所示:
(require (for-syntax racket/base syntax/transformer))
(define-syntax-rule (def name val)
(begin
(define tmp val)
(define-syntax name
(make-variable-like-transformer
#'(begin0 tmp (set! tmp (not tmp)))))))
(本版本使用begin0
获取更新前的值,当然你也可以改回来。)
如果您确实希望 a_
变量可访问,则可以使用 format-id
创建名称,但不能再使用 define-syntax-rule
来创建名称。
(require (for-syntax racket/base racket/syntax syntax/transformer))
(define-syntax (def stx)
(syntax-case stx ()
[(def name val)
(with-syntax ([name_ (format-id #'name "~a_" #'name)])
#'(begin
(define name_ val)
(define-syntax name
(make-variable-like-transformer
#'(begin0 name_ (set! name_ (not name_)))))))]))