Для записи в Chez Scheme с множеством числовых полей, которые постоянно изменяются небольшими увеличениями и уменьшениями, обычно на единицу, есть ли способ написать макрос, который может изменять значение поля, передавая ему это поле? Сейчас я делаю это примерно так, как в следующем транскрипте REPL:

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

> (define-record-type r (fields (mutable x) (mutable y))
                                ;; и так далее...
                                ))
> (define my-r (make-r 3 5
                       ;; и так далее...
                       ))
> (r-x-set! my-r (+ (r-x my-r) 1))
> my-r
#[#{r gak6l6ll8wuv7yd61kiomgudo-2} 4 5]

Хотелось бы иметь простой макрос, скажем inc!, который мог бы выполнять мутирующие операции увеличения/уменьшения полей в записи. Я начал с чего-то вроде Scheme-версии Lisps incf и decf,

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

(inc! (r-x my-r)) ;; Синтаксическая ошибка

Это работает для "обычных" переменных (и упрощает реализацию dec!), но не использует механизм установки изменяемых полей записи, r-x-set! в данном случае.

Есть ли очевидный способ написать такой макрос? Такой, где можно просто передать ссылку на поле записи без необходимости писать что-то отдельное для каждого поля?

Ответы (1)

Вы можете построить мутатор -set! из заданного аксессора. Это можно сделать, преобразовав символ аксессора в строку и добавив к ней "-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! создает символ, который уже связан с процедурой-мутатором. Этот символ можно было бы связать с mut! напрямую, но тогда при оценке выражения (mut! rec (+ (acc rec) n)) возникнет исключение, поскольку mut! теперь оценивается как символ, например, r-x-set!. Мы хотим, чтобы mut! оценивался как процедура в вызове процедуры. Вызвав сначала eval на построенном символе, мы получим процедуру-мутатор, которая привязана к этому символу, привязав ее к mut! вместо символа.

Здесь приведено взаимодействие REPL, которое иллюстрирует проблему и, надеюсь, поможет внести ясность:

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

Исключение: попытка применить непроцедуру +
Введите (debug) для входа в отладчик.
> (define f (eval (string->symbol "+")))
> f
#
> (f 1 2)
3

2022 WebDevInsider