Make-variable-like-transformer

Why does the following give an error?

state-a: undefined;
cannot reference an identifier before its definition
in module: 'anonymous-module

Wrong use of datum->syntax?
How to fix this?

#lang racket

(require (for-syntax (only-in syntax/transformer make-variable-like-transformer)))

(struct state (a) #:mutable #:constructor-name make-state #:omit-define-syntaxes)
(define state (make-state 1))

(define-syntax (state-component stx)
  (syntax-case stx ()
    ((_ id)
     (with-syntax
       ((state-id
          (datum->syntax stx
           (string->symbol (format "state-~s" (syntax-e #'id)))))
        (set-state-id!
          (datum->syntax stx
           (string->symbol (format "set-state-~s!" (syntax-e #'id))))))
       #'(define-syntax id
           (make-variable-like-transformer
             (state-id state)
             (λ (v) (set-state-id! state v))))))))

(state-component a)

I think the problem is easier to see if we replace (state-component a) with its expansion, as reported by the macro stepper:

#lang racket

(require (for-syntax (only-in syntax/transformer
                              make-variable-like-transformer)))

(struct state (a)
  #:mutable
  #:constructor-name make-state
  #:omit-define-syntaxes)
(define state (make-state 1))

(define-syntax a
  (make-variable-like-transformer
   (state-a state)
   (λ (v)
     (set-state-a! state v))))

This is trying to use state, state-a, and set-state-a! at compile time, but those values don’t exist until runtime. You need to pass make-variable-like-transformer a syntax-quoted #'(state-a state)[1] that it should expand to, not the value of the expression (state-a state).

Here is a working version, with only two other changes I couldn’t resist: generating names is much better with format-id, and using the scope from #'id rather than stx is more robust if some other macro might expand to state-component:

#lang racket

(require (for-syntax (only-in racket/syntax
                              format-id)
                     (only-in syntax/transformer
                              make-variable-like-transformer)))

(module+ test
  (require rackunit)
  (check-eqv? a 1)
  (set! a 5)
  (check-eqv? a 5))

(struct state (a)
  #:mutable
  #:constructor-name make-state
  #:omit-define-syntaxes)
(define state (make-state 1))

(define-syntax (state-component stx)
  (syntax-case stx ()
    [(_ id)
     (with-syntax
         ([state-id (format-id #'id "state-~a" #'id)]
          [set-state-id! (format-id #'id "set-state-~a!" #'id)])
       #`(define-syntax id
           (make-variable-like-transformer
            #'(state-id state)
            #'(λ (v)
                (set-state-id! state v)))))]))

(state-component a)

Looking beyond the immediate problem, I’d guess that you have written define-state-component because, in reality, you have several components. I am allergic to writing out lists of fields multiple times—in the struct declaration, in the arguments to make-state, and in the uses of define-state-component—and I’d be sad that DrRacket’s scope-aware renaming couldn’t handle a. I would be inclined to do something like this:

#lang racket

(require (for-syntax racket/syntax
                     syntax/transformer)
         syntax/parse/define)

(module+ test
  (require rackunit)
  (check-eqv? a 1)
  (set! a 5)
  (check-eqv? a 5)
  (check-equal? b '(a b c))
  (set! b 'done)
  (check-eq? b 'done))

(define-syntax-parse-rule (define-state name:id
                            (~seq field:id init-val:expr) ...)
  #:with make-name (format-id #'name "make-~a" #'name #:subs? #t)
  #:with ((~seq field-ref set-field!) ...)
  (for*/list ([fld (in-list (attribute field))]
              [fmt `("~a-~a" "set-~a-~a!")])
    (format-id fld fmt #'name fld))
  (begin
    (struct name (field ...)
      #:mutable
      #:constructor-name make-name
      #:omit-define-syntaxes)
    (define name
      (make-name init-val ...))
    (define-syntax field
      (make-variable-like-transformer
       (quote-syntax (field-ref name))
       (quote-syntax (λ (v)
                       (set-field! name v)))))
    ...))

(define-state state
  a 1
  b '(a b c))

In real life, I did something like this to define the web server’s “safety limits” construct with make-safety-limits and make-unlimited-safety-limits.

Often, when we talk about why macros are so useful, we give examples of using macros to implement new linguistic constructs. But I think the macro that is used exactly once is an underappreciated genre!


  1. More correctly, (quote-syntax (state-a state)), but we can ignore that in this case. ↩︎

Thanks for your rapìd reply. It works (of course) Including the definition of the struct in the macro such as to avoid repeated lists of field-names, is a good idea, Makes adding or removing fields in new versions easier and less work.
Jos Koot