macrosschemechez-schemedefine-syntax

How to increment a Record Field using Scheme define-syntax macro


Given a Chez Scheme record with many numeric fields that are contantly being mutated by small increments and decrements, usually by one, is there a way to write a macro that can mutate a field value by passing it the field? The way I accomplish this now is something like the following REPL transcript:

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]

It would be nice to have a simple macro, say inc!, that could do the mutating increment/decrement operations on the fields in the record. I started with something like a Scheme version of Lisps incf and decf,

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

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

Which works for "normal" variables (and makes it easy to implement dec!), but it doesn't use the mechanism to set mutable record fields, r-x-set! in this case.

Is there an obvious way to write such a macro? One where you can just pass a reference to the record field without having to write something different for each field?


Solution

  • You can construct a -set! mutator from the given accessor. This can be done by converting the symbol for the accessor to a string and appending "-set!" to it. Then eval can be used to get the actual mutator procedure. Here is a macro that increments a specified field by some amount 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)))]))
    

    This can be used to create an inc! macro:

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

    But, it would be nice to be able to increment multiple fields at the same time; here are inc! and dec! macros that do that:

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

    Sample interaction:

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

    A Note on the Use of eval

    The increment-n! macro constructs a symbol which has already been bound to a mutator procedure. That symbol could then be bound to mut! directly, but then when the expression (mut! rec (+ (acc rec) n)) is evaluated an exception would be raised since mut! now evaluates to a symbol, e.g., r-x-set!. We want mut! to evaluate to a procedure in a procedure call. By calling eval on the constructed symbol first we get the mutator procedure which is bound to that symbol, binding it to mut! instead of the symbol.

    Here is a REPL interaction that illustrates the problem, and will hopefully help to clarify:

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