[racket-dev] Access to the structure internal API within define-struct's body
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?