【问题标题】:Dynamically define symbols and macros using a racket macro使用球拍宏动态定义符号和宏
【发布时间】:2021-09-15 09:39:01
【问题描述】:

我正在 Racket 中创建一种 DSL(领域特定语言)。在这个 DSL 中,访问任何值都会导致它发生变异。换句话说,没有什么是纯粹的。我试图了解如何使用单个球拍宏定义几个新符号和宏,以尝试减少重复代码。该宏的一部分要求我引用私有/隐藏值。例如。当我引用a 时,它实际上是一个宏,它为私有值a_ 调用getter get-val(类似于普通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 宏,它允许我检查值而不会为了调试目的而改变,但一旦我知道如何创建第一个宏,我可能自己就能弄清楚。

【问题讨论】:

    标签: dynamic macros lisp racket


    【解决方案1】:

    Racket 称他们为"identifier macros"。实际上,无论是“在操作员位置”还是“像变量”,Racket 宏扩展器都会调用宏的转换器;不同之处在于转换器(通常使用syntax-rulessyntax-casesyntax-parse 实现)接受的使用模式。

    您是否需要像 a_ 这样的私有变量才能访问?如果不是,您可以只使用一个常量名称,例如tmp,而hygiene 将区分(def a #f) 定义的tmp(def b #f) 定义的tmp。然后,您可以使用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_)))))))]))
    

    【讨论】:

      【解决方案2】:

      我设法使用namespace-anchorseval 组合了一个有效的解决方案。我想可能有更好的方法来做到这一点。这是功能代码。

      (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)
      

      【讨论】:

      • 这只是 REPL 中的“功能”。如果你有一个合适的 Racket 模块(即程序的第一行是#lang racket),它就不会工作。而且,它只适用于“顶级上下文”。例如,如果你有(let () (def 'a #f) (println a)),你会得到一个错误。 @Ryan Culpepper 上面的回答是解决这个问题的正确方法。
      猜你喜欢
      • 1970-01-01
      • 2013-04-10
      • 1970-01-01
      • 2012-04-09
      • 1970-01-01
      • 2012-01-07
      • 2017-02-03
      • 1970-01-01
      • 2022-12-18
      相关资源
      最近更新 更多