Scheme 在Racket中使用自身内部的语法宏

Scheme 在Racket中使用自身内部的语法宏,scheme,lisp,racket,Scheme,Lisp,Racket,如果以前有人问过这个问题,我很抱歉-我正在尝试构建一个类似于struct的宏,我有点卡住了 基本思想是能够为语法规则提供一个hash对象,然后为hash中的每个键编写getter和setter。例如: (Get obj bio firstName) ==> (get obj '(bio firstName)) (define-syntax-rule (Get obj sym ...) (get obj (quote (sym ...)))) (hasheq的名称(hasheq的“用户”

如果以前有人问过这个问题,我很抱歉-我正在尝试构建一个类似于
struct
的宏,我有点卡住了

基本思想是能够为语法规则提供一个hash对象,然后为hash中的每个键编写getter和setter。例如:

(Get obj bio firstName) ==> (get obj '(bio firstName))

(define-syntax-rule (Get obj sym ...) (get obj (quote (sym ...))))
(hasheq的名称(hasheq的“用户”)
“isOnline(hasheq”B#t)
bio(hasheq'M(hasheq'firstName)(hasheq'S“Sally”)
姓(哈什克的“华莱士”))
调用我的宏:

(dynamo型号使访问者成为用户)
应生成以下生成的方法:

用户名
用户在线
用户简历
用户名
用户名
这就是我目前所拥有的

(定义语法(dynamo model make accessors stx)
(语法大小写stx()
[(uu)前缀字段)
#`(开始
#,@(for/list([kv(散列->列表(eval(语法->数据字段))))
(带语法*([钥匙(汽车kv)]
[getter(格式id#'prefix'~a-~a#'prefix#'key)]
[类型(car(散列键(cdr-kv)))]
(displayln#getter)
(如果(等式?(语法->基准类型)M)
(dynamo模型生成访问器前缀(hash ref(cdr kv)'M))
#`(定义(gettero)
(+ 1 1))))))]))
当我试图解释嵌套映射时,问题就出现了。我尝试在其内部调用语法规则,得到:

dynamo-model-make-accessors: undefined;
 cannot reference an identifier before its definition
如何递归调用我的宏

编辑(最新代码):


最初是一条评论,但空间不足:

我不排除有人可以欺骗系统的可能性,但是

宏系统使用阶段。在编译时(可能是星期二),系统有一组绑定。编译程序时,编译期间使用的绑定/值将消失。然后在运行时(可能是星期三)运行生成的程序

但它有点复杂。如果在编译时使用宏,则需要先编译编译时程序(在编译时)。因此,宏
foo
不能使用
foo
——因为要使用
foo
foo
需要编译,而要做到这一点,
foo
需要编译——并且

您可以做的是扩展到使用
foo
。 也就是说,您可以扩展到使用
foo
的东西。 当
foo
作为结果返回语法对象时,扩展器将执行 并继续扩大。当扩展器看到
foo
的新用法时,它会再次调用与
foo
关联的语法转换器


使用
eval
表示您试图绕过这些阶段。这是一场艰苦的战斗,也许值得重新思考当前的方法。

你不能在Racket中编写宏。在Racket中,程序的绑定结构(绑定的名称和作用域)不能依赖于运行时值

一个为什么这不起作用的例子,考虑这个例子:

(define (get-name h obj)
  (dynamo-model-make-accessors user h)
  (user-name obj))

(get-name (hasheq 'name (hasheq 'S ___)) 'some-object)
(get-name (hasheq) 'another-object)
get name
中使用
dynamo模型make访问器必须编译一次。但是,当实际值在函数被调用之前不可用时,它如何决定绑定什么名称呢?当它在运行时可能得到多个不同的值时

相反,您可以更动态或更静态

动态 只需编写一个函数,获取符号列表,并在运行时执行引用(和检查)。因此,您可以编写类似于
(get obj'(bio firstName))
的内容,而不是
(user bio firstName obj)

然后,您可以通过制作一个添加引号的宏(在这里我们称之为
Get
),在其周围添加一点语法糖。例如:

(Get obj bio firstName) ==> (get obj '(bio firstName))

(define-syntax-rule (Get obj sym ...) (get obj (quote (sym ...))))
静止的 另一种方法是使宏生成的绑定结构依赖于编译时信息而不是运行时信息。这里有一种方法:

;; A RecordSpec is (Hasheq Symbol => FieldSpec)
;; A FieldSpec is one of
;; - (hasheq 'S String)
;; - (hasheq 'M RecordSpec)

;; Here we bind `user-fields` to a *compile-time* RecordSpec value
;; using `define-syntax`.

(define-syntax user-fields
  (hasheq 'name (hasheq 'S "user")
          'isOnline (hasheq 'B #t)
          'bio (hasheq 'M (hasheq 'firstName (hasheq 'S "Sally")
                                  'lastName (hasheq 'S "Wallace")))))
宏可以通过调用
语法本地值
获取与标识符关联的编译时RecordSpec值

(define-syntax (dynamo-model-make-accessors stx)
  (syntax-parse stx
    [(_ prefix spec:id)

     ;; generate-recordspec-bindings : Identifier RecordSpec
     ;;                             -> (Listof Syntax[Definition])
     (define (generate-recordspec-bindings prefix-id recordspec)
       (append*
        (for/list ([(field fieldspec) (in-hash recordspec)])
          (define field-id (format-id prefix-id "~a-~a" prefix-id field))
          (generate-fieldspec-bindings field-id fieldspec))))

     ;; generate-fieldspec-bindings : Identifier RecordSpec
     ;;                            -> (Listof Syntax[Definition])
     (define (generate-fieldspec-bindings field-id fieldspec)
       (cond [(hash-ref fieldspec 'S)
              => (lambda (string-value)
                   ;; left as exercise to reader
                   ___)]
             [(hash-ref fieldspec 'M)
              => (lambda (inner-recordspec)
                   ;; left as exercise to reader
                   ___)]))

     (define recordspec (syntax-local-value #'spec)) ;; better be a RecordSpec
     #`(begin
         #,@(generate-recordspec-bindings #'prefix recordspec))]))
然后您可以像这样使用宏:

(define (get-name obj)
  (dynamo-model-make-accessors user user-fields) ;; defines `user-name`, etc
  (user-name obj))

谢谢你的回复。我接受了你的建议,并扩展到使用我自己的宏。请检查上面编辑的源代码。我同意您使用
eval
是错误的。不幸的是,我似乎找不到很多替代方案…我特别困惑为什么有时我需要使用#来访问模式变量,有时它们在没有任何读取器糖的情况下工作,有时我得到“模式变量不能在模板之外使用”…我一直在试图弄清楚这是为什么,但是到目前为止,
eval
是最简单的解决方法。模式变量绑定到一段语法。当您使用
(syntax template)
aka
#“template
创建新的语法对象时,所有出现的模式变量都将替换为它们所绑定的对象。想象一下,模式变量并不特别。那么,在这样的模板中(列表x)
扩展器如何知道要替换
x
,而
列表
不是?问题:在
(dynamo model make accessors user)
中,
是文字吗?宏通过计算静态信息来工作。如果
是一个文本,那么做你想做的事情其实很容易。但是,如果
是一个只有在运行时求值后才会显示的表达式,则宏不可能执行此操作(由于下面提到的阶段系统@soegaard)。@SoraweePorncharoenwase它是一个在宏中求值的表达式(因此
eval
)。然而,我很好奇,如何将hashmap构建为l