2
votes

I am creating a DSL (Domain specific language) in Racket. In this DSL accessing any value causes it to mutate. In other words, nothing is pure. I am trying to understand how to define several new symbols and macros using a single racket macro to try and reduce duplicated code. Part of this macro requires me to reference a private/hidden value. E.G. when I reference a it is actually a macro which calls the getter get-val for the private value a_ (Analogous to a symbol-macro in common lisp). I've tried to follow along with the documentation for generating macros with macros here but it doesn't really seem cover how to a define a symbol named a_ given one named a because it's somewhat tangential. Digging through SO has also not led me to much, though I'm not sure what the functionality I'm looking for is called.

Each variable uses the same getter macro which mutates the value in the process. This works fine.

(define-syntax-rule (get-val x)
  (begin (set! x (not x)) x))

In the original code I would need to manually define a getter macro for each symbol like this. This also works fine but leads to lots of duplicate code.

(define a_ #f)
(define-syntax (a stx) #'(get-val a_))

(define b_ #f)
(define-syntax (b stx) #'(get-val b_))

I would prefer to define a master macro once which looks something like this. (Not working). I believe that string->symbol is likely the issue.

(define-syntax-rule (def name val)
  (begin
    ; Create new varname with name followed by underscore
    ; This should probably be a `let` ?
    (define name_ (string-append name "_"))
    ; Assign the value to that (private) symbol.
    (define (string->symbol name_) val)
    ; Create public getter macro
    (define-syntax (name stx) #'(get-val (string->symbol name_)))))

Then I can simply create lots of values like this

(def 'a #f)
(def 'b #f)
; or ideally this (not quoted)
(def a #f)
(def b #f)

Once this is working I will also create a peek macro which allows me to inspect the value without mutating for debugging purposes, though I can probably figure that out myself once I know how to create the first macro.

2

2 Answers

6
votes

Racket calls them "identifier macros". Actually, the Racket macro expander calls a macro's transformer whether it's used "in operator position" or "like a variable"; the difference is what patterns of usage the transformer (usually implemented with syntax-rules, syntax-case, or syntax-parse) accepts.

Do you need the private variables like a_ to be accessible? If not, you can just use a single constant name, like tmp, and hygiene will distinguish the tmp defined by (def a #f) from the tmp defined by (def b #f). Then you can use make-variable-like-transformer to turn a variable-like use of the defined name into an expression that gets and updates the value, like this:

(require (for-syntax racket/base syntax/transformer))
(define-syntax-rule (def name val)
  (begin
   (define tmp val)
   (define-syntax name
     (make-variable-like-transformer
       #'(begin0 tmp (set! tmp (not tmp)))))))

(This version uses begin0 to get the value before updating it, but you can change it back, of course.)

If you do want the a_ variables to be accessible, then you can use format-id to create the name, but you can no longer use define-syntax-rule to do it.

(require (for-syntax racket/base racket/syntax syntax/transformer))
(define-syntax (def stx)
  (syntax-case stx ()
    [(def name val)
     (with-syntax ([name_ (format-id #'name "~a_" #'name)])
       #'(begin
           (define name_ val)
           (define-syntax name
             (make-variable-like-transformer
              #'(begin0 name_ (set! name_ (not name_)))))))]))
1
votes

I managed to hack together a functioning solution using namespace-anchors and eval. I imagine there is probably a much better way to do this. Here is the functioning code.

(define-namespace-anchor anc)
(define ns (namespace-anchor->namespace anc))

(define-syntax-rule (get-val x)
  (begin (set! x (not x)) x))

(define-syntax-rule (def name val)
  (begin
    (define name_ (string-append (symbol->string name) "_"))
    (eval `(define ,(string->symbol name_) ,val)
          ns)
    (eval `(define-syntax
             (,name stx)
             #'(get-val ,(string->symbol name_)))
          ns)))

(def 'a #f)
(def 'b #f)