如何使用 Scheme 定义语法宏增加记录字段

问题描述

给定一个 Chez Scheme 记录,其中包含许多数字字段,这些字段通过小的增量和减量不断变化,通常是一个,有没有办法编写一个宏,通过将字段传递给字段值来改变字段值?我现在完成此操作的方式类似于以下 REPL 成绩单:

Chez Scheme Version 9.5.4
copyright 1984-2020 Cisco Systems,Inc.

> (define-record-type r (fields (mutable x) (mutable y)
                                ;; and so on...
                                ))
> (define my-r (make-r 3 5
                       ;; and so on...
                       ))
> (r-x-set! my-r (+ (r-x my-r) 1))
> my-r
#[#{r gak6l6ll8wuv7yd61kiomgudo-2} 4 5]

最好有一个简单的宏,比如 inc!,它可以对记录中的字段执行变异的递增/递减操作。我开始使用类似 Lisps incfdecf 的 Scheme 版本,

(define-Syntax inc!
    (Syntax-rules ()
      ((_ x) (begin (set! x (+ x 1)) x))))

(inc! (r-x my-r)) ;; Syntax error

这适用于“普通”变量(并使其易于实现 dec!),但它不使用设置可变记录字段的机制,在本例中为 r-x-set!

有没有明显的方法可以写出这样的宏?一种可以只传递对记录字段的引用而不必为每个字段编写不同内容的地方?

解决方法

您可以从给定的访问器构造一个 -set! mutator。这可以通过将访问器的符号转换为字符串并向其附加 "-set!" 来完成。然后可以使用 eval 来获取实际的增变程序。这是一个将指定字段增加某个量 n 的宏:

(define-syntax increment-n!
  (syntax-rules ()
    [(_ (acc rec) n)
     (let* ((acc-name (symbol->string (quote acc)))
            (mut-name (string-append acc-name "-set!"))
            (mut! (eval (string->symbol mut-name))))
       (mut! rec (+ (acc rec) n)))]))

这可用于创建 inc! 宏:

(define-syntax inc!
  (syntax-rules ()
    [(_ (acc rec)) (increment-n! (acc rec) 1)]))

但是,能够同时增加多个字段会很好;以下是执行此操作的 inc!dec! 宏:

(define-syntax inc!
  (syntax-rules ()
    [(_ (acc rec) ...) (begin (increment-n! (acc rec) 1) ...)]))

(define-syntax dec!
  (syntax-rules ()
    [(_ (acc rec) ...) (begin (increment-n! (acc rec) -1) ...)]))

示例交互:

> my-r
#[#{r n5an6pxs3wvid36v2gvn8z9zo-5} 3 5 7]
> (inc! (r-x my-r))
> my-r
#[#{r n5an6pxs3wvid36v2gvn8z9zo-5} 4 5 7]
> (dec! (r-z my-r))
> my-r
#[#{r n5an6pxs3wvid36v2gvn8z9zo-5} 4 5 6]
> (inc! (r-x my-r) (r-y my-r) (r-z my-r))
> my-r
#[#{r n5an6pxs3wvid36v2gvn8z9zo-5} 5 6 7]

使用 eval 的注意事项

increment-n! 宏构造了一个已经绑定到 mutator 过程的符号。然后可以将该符号直接绑定到 mut!,但是当计算表达式 (mut! rec (+ (acc rec) n)) 时,会引发异常,因为 mut! 现在计算为一个符号,例如 r-x-set! .我们希望 mut! 在过程调用中对过程求值。通过首先在构造的符号上调用 eval,我们得到绑定到该符号的 mutator 过程,将其绑定到 mut! 而不是符号。

这是说明问题的 REPL 交互,希望有助于澄清:

> (define f (string->symbol "+"))
> f
+
> (f 1 2)

Exception: attempt to apply non-procedure +
Type (debug) to enter the debugger.
> (define f (eval (string->symbol "+")))
> f
#<procedure +>
> (f 1 2)
3