简体   繁体   English

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

[英]Using a syntax macro inside of itself in Racket

Apologies if this has been asked before - I'm trying to build a macro similar to struct and I'm a bit stuck.抱歉,如果之前有人问过这个问题 - 我正在尝试构建一个类似于struct的宏,但我有点卡住了。

The basic idea is to be able to provide a hash object to my syntax rule that should then write getters and setters for every key in the hash.基本思想是能够为我的语法规则提供一个散列对象,然后应该为散列中的每个键编写 getter 和 setter。 For example:例如:

(hasheq 'name (hasheq 'S "user")
        'isOnline (hasheq 'B #t)
        'bio (hasheq 'M (hasheq 'firstName (hasheq 'S "Sally")
                                'lastName (hasheq 'S "Wallace"))))

Calling my macro:调用我的宏:

(dynamo-model-make-accessors user <my-hash-object>)

Should result in the following generated methods:应该导致以下生成的方法:

user-name
user-isOnline
user-bio
user-bio-firstName
user-bio-lastName

This is what I have so far.这是我到目前为止。

(define-syntax (dynamo-model-make-accessors stx)
    (syntax-case stx ()
        [(_ prefix fields)
            #`(begin
                    #,@(for/list ([kv (hash->list (eval (syntax->datum #'fields)))])
                            (with-syntax* ([key (car kv)]
                                               [getter (format-id #'prefix "~a-~a" #'prefix #'key)]
                                                [type (car (hash-keys (cdr kv)))])
                                    (displayln #'getter)
                                    (if (eq? (syntax->datum #'type) 'M)
                                        (dynamo-model-make-accessors #'prefix (hash-ref (cdr kv) 'M))
                                        #`(define (getter O)
                                            (+ 1 1))))))]))

The problem comes in when I try to interpret the nested map.当我尝试解释嵌套地图时,问题就出现了。 I try to call my syntax rule within itself and I get:我尝试在其内部调用我的语法规则,我得到:

dynamo-model-make-accessors: undefined;
 cannot reference an identifier before its definition

How can I recursively call my macro?如何递归调用我的宏?

EDIT (newest code):编辑(最新代码):

(define-syntax (dynamo-model-make-accessors stx)
    (syntax-parse stx
        [(_dynamo-model-make-accessors prefix fields)
            (define exprs (for/list ([kv (hash->list (eval #'fields))])
                                    (with-syntax* ([key (car kv)]
                                                       [getter (format-id #'prefix "~a-~a" (eval #'prefix) #'key)]
                                                        [type (car (hash-keys (cdr kv)))]
                                                        [value (hash-ref (cdr kv) (syntax->datum #'type))])
                                        (displayln #'getter)
                                        (displayln #'type)
                                        (if (eq? (syntax->datum #'type) 'M)
                                            #`(dynamo-model-make-accessors #'getter value)
                                            #`(define (getter O)
                                                (+ 1 1))))))
            (with-syntax ([(expr ...) exprs])
                #'(begin expr ...))]))

You cannot write that macro in Racket.您不能在 Racket 中编写该宏。 In Racket, a program's binding structure (what names are bound and what scopes they have) cannot depend on run-time values .在 Racket 中,程序的绑定结构(绑定的名称以及它们具有的作用域)不能依赖于运行时值

For an example of why this doesn't work, consider this example:有关为什么这不起作用的示例,请考虑以下示例:

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

The use of dynamo-model-make-accessors in get-name must be compiled once .get-name使用dynamo-model-make-accessors必须编译一次 But how can it decide what names to bind when the actual value isn't available until the function gets called?但是当实际在函数被调用之前不可用时,它如何决定绑定哪些名称? And when it might get multiple, different values at run time?什么时候它可能在运行时获得多个不同的值?

Instead, you can either go more dynamic or more static.相反,您可以更动态或更静态。

Dynamic动态的

Just write a function that takes a list of symbols and does the references (and checks) at run time.只需编写一个函数,该函数接受一个符号列表并在运行时执行引用(和检查)。 So instead of (user-bio-firstName obj) you would write something like (get obj '(bio firstName)) .因此,您可以编写类似(get obj '(bio firstName))类的内容,而不是(user-bio-firstName obj) (get obj '(bio firstName))

Then you can add a little syntactic sugar around it by making a macro (let's call it Get here) that adds quotes.然后,您可以通过创建一个添加引号的宏(我们称之为Get here)来在它周围添加一些语法糖。 For example:例如:

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

(define-syntax-rule (Get obj sym ...) (get obj (quote (sym ...))))

Static静止的

The other way is to make the binding structure your macro generates depend on compile-time information rather than run-time information.另一种方法是使宏生成的绑定结构依赖于编译时信息而不是运行时信息。 Here's most of one way to do it:这是大多数的一种方法:

;; 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")))))

The macro can fetch the compile-time RecordSpec value associated with an identifier by calling syntax-local-value .该宏可以通过调用syntax-local-value获取与标识符关联的编译时 RecordSpec syntax-local-value

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

Then you can use the macro like this:然后你可以像这样使用宏:

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

Originally a comment, but ran out of space:原本是一条评论,但空间不足:

I won't rule out the possibility that one can cheat the system, but ... .我不排除有人可以欺骗系统的可能性,但是......

The macro system uses phases.宏系统使用阶段。 At compile time (could be tuesday) the system has one set of bindings.在编译时(可能是星期二),系统有一组绑定。 When the program is compiled the bindings/values used during compilation are gone.编译程序时,编译期间使用的绑定/值消失了。 Then at runtime (could be wednesday) the resulting program is run.然后在运行时(可能是星期三)运行生成的程序。

But it is a bit more complicated.但它有点复杂。 If at compile time you use a macro, then your compile time program needs to be compiled first (at compile-compile-time).如果在编译时使用宏,则需要首先编译编译时程序(在 compile-compile-time)。 A macro foo can therefore not use foo - because in order to use foo , foo needs to be compiled - and to do that foo needs to be compiled - and ...因此宏foo不能使用foo - 因为为了使用foofoo需要被编译 - 要做到这一点, foo需要被编译 - 并且......

What you can do, is to expand into a use of foo .您可以做的是扩展为使用foo That is you can expand into something that uses foo .也就是说,您可以扩展为使用foo When foo returns a syntax object as a result, the expander takes the result and continues to expand it.foo作为结果返回一个语法对象时,扩展器获取结果并继续扩展它。 When the expander sees the new use of foo , it calls the syntax transformer associated with foo again.当扩展器看到foo的新用法时,它会再次调用与foo关联的语法转换器。

The use of eval shows that you are attempting to circumvent the phases. eval的使用表明您正试图绕过这些阶段。 It is an uphill battle, and it might be worth rethinking the current approach.这是一场艰苦的战斗,值得重新考虑当前的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM