[racket-dev] Access to the structure internal API within define-struct's body

From: Danny Yoo (dyoo at cs.wpi.edu)
Date: Thu Jul 7 16:29:21 EDT 2011

I'm doing some experiments with structures.  I'm trying to write a
macro that lets me create structures that support runtime, dictionary
lookup.  I've started with:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#lang racket
(require (for-syntax racket/syntax))

(define-syntax (def-struct stx)
  (syntax-case stx ()
    [(_ name (id ...))
     (with-syntax ([(id-ref ...)
                    (map (lambda (id)
                           (format-id #'stx "~a-~a" #'name (syntax-e id)))
                         (syntax->list #'(id ...)))])
       (quasisyntax/loc stx
         (begin
           (define-struct name (id ...)
             #:property prop:procedure
             (lambda (a-struct field-name)
               (cond
                 [(eq? field-name 'id)
                  (id-ref a-struct)]
                 ...
                 [else
                  (error 'name "Unknown field ~a" field-name)]))))))]))

;; Example usage
(def-struct person (name age))
(define p (person 'bob 32))
(p 'name)
(p 'age)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

This sorta works, although it may have some hygiene issues.  I also
feel awkward about the use of the id-refs in the macro above because
it's mixing internal and external API usages.  I already know that, at
the point of the procedure, that the value is of the right struct
type.  The public-facing accessors, though, make no assumptions. I
want to get at the internal interface of the structure, the values
provided by make-struct-type, within the definition of a struct.



I can throw caution to the wind and do the following:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#lang racket
(require racket/unsafe/ops)

(define-syntax (def-struct stx)
  (syntax-case stx ()
    [(_ name (id ...))
     (syntax/loc stx
       (define-struct name (id ...)
         #:property prop:procedure
         (lambda (a-struct field-name)
           (cond
             [(eq? field-name 'id)
              (unsafe-struct-ref a-struct (struct-field-index id))]
             ...
             [else
              (error 'name "Unknown field ~a" field-name)]))))]))

(def-struct person (name age))
(define p (person 'bob 32))
(p 'name)
(p 'age)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

where I use struct-field-index to figure out how to index the
structure.  This feels better than what I had earlier, with less
syntax tricks, though this too makes me feel a little dirty.  I don't
want to touch racket/unsafe/ops unless I really have to.


struct-field-index is a syntax parameter that exists as a form to help
writers of prop:procedure procedures.  But as far as I can tell, its
use is limited to passing a number directly to prop:procedure.  What I
don't see are equivalent simple syntactic forms to let me get at the
internal constructor, predicate, accessor, and mutator functions from
within a struct definition, and I think that's what's missing.  If the
internal make-, ?, -ref, and -set! bindings were similarly exposed as
syntax parameters for the syntactic context of a struct definition,
then that would address this API awkwardness.


Here's what the change looks like:

    https://github.com/dyoo/racket/commit/38c41b34f22c60d057bcbaa4784a87bf768ee2cf


This adds syntax-parameters struct-field-ref and struct-field-set!.
Then the definition I had earlier changes to this:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#lang racket

(define-syntax (def-struct stx)
  (syntax-case stx ()
    [(_ name (id ...))
     (syntax/loc stx
       (define-struct name (id ...)
         #:property prop:procedure
         (lambda (a-struct field-name)
           (cond
             [(eq? field-name 'id)
              (struct-field-ref a-struct (struct-field-index id))]
             ...
             [else
              (error 'name "Unknown field ~a" field-name)]))))]))

(def-struct person (name age))
(define p (person 'bob 32))
(p 'name)
(p 'age)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

The change allows me to reuse define-struct more readily, without
having to go through make-struct-type, unhygienic syntax, or unsafe
operations.


Is exposing the internal -ref, -set!, make- and predicate bindings
within a define-struct's body worthwhile to other people?


Posted on the dev mailing list.