Related to this issue of functional updaters, I've been working on a
new struct mechanism based on my old "super structs" [1].
"super structs" was an attempt to give a lot more control of the names
created by the struct macro and give a way to use keyword arguments in
constructors. I was hoping to eventually support auto values more like
optional arguments, but didn't do it.
The new system, records [2], is an attempt to give a lot more
information about the structure statically so that functional updating
(including down to sub-types) is easier. Part of the way it does this
is by removing all but a single name from the output of the macro and
using keywords instead for everything. Here's the test suite to give a
feel for it:
(let ()
(record posn (#:x #:y))
;; Creation
(define p0 (posn #:x 1 #:y 3))
;; Identification
(check-true (posn #:? p0))
(check-false (posn #:? 3))
;; Accessor
(check-equal? (posn p0 #:x) 1)
(check-equal? (posn p0 #:y) 3)
;; Updating
(define p1 (posn p0 #:x 2))
(check-equal? (posn p1 #:x) 2)
(check-equal? (posn p1 #:y) 3)
(define p2 (posn p0 #:y 4))
(check-equal? (posn p2 #:x) 1)
(check-equal? (posn p2 #:y) 4)
(define p3 (posn p0 #:y 4 #:x 2))
(check-equal? (posn p3 #:x) 2)
(check-equal? (posn p3 #:y) 4)
;; Matching
(check-equal? (match p1 [(posn #:x x #:y y) (list x y)])
(list 2 3))
(check-equal? (match p1 [(posn #:y y #:x x) (list x y)])
(list 2 3))
(check-equal? (match p1 [(posn #:x x) (list x 3)])
(list 2 3))
(check-equal? (match p1 [(posn) (list 2 3)])
(list 2 3)))
(let ()
(record posn ([#:x #:mutable] #:y))
(define p0 (posn #:x 1 #:y 3))
(check-equal? (posn p0 #:x) 1)
;; Mutation
(posn p0 #:x 2 #:!)
(check-equal? (posn p0 #:x) 2))
(let ()
;; Whole record is mutable
(record posn (#:x #:y) #:mutable)
(define p0 (posn #:x 1 #:y 3))
(check-equal? (posn p0 #:x) 1)
(check-equal? (posn p0 #:y) 3)
(posn p0 #:x 2 #:!)
(posn p0 #:y 4 #:!)
(check-equal? (posn p0 #:x) 2)
(check-equal? (posn p0 #:y) 4)
(posn p0 #:x 1 #:y 3 #:!)
(check-equal? (posn p0 #:x) 1)
(check-equal? (posn p0 #:y) 3))
One of the issues with this approach is that parents and children
can't have fields with the same names because they exist in the same
global keyword scope.
Similarly, the normal scoping mechanisms of Racket would be
ineffective at protecting internal fields and, more importantly,
hidden mutation to them. I'm imaging a "record-close" operation that
produces a new macro with restricted access, for instance:
(record posn (#:x #:y))
(record-close posn-with-no-y #:base posn (#:x))
Ironing out all the details of this new approach is a lot of work and
I'm not totally convinced myself if it is a good idea. It's along
enough that it would be valuable to hear anyone's take on its flaws or
merits.
Finally, budding macrologists may be interested in some of the
implementation tricks :)
Jay
1.
https://github.com/jeapostrophe/exp/blob/master/sstruct-tests.rkt
2.
https://github.com/jeapostrophe/exp/blob/master/record.rkt
--
Jay McCarthy <
j...@cs.byu.edu>
Assistant Professor / Brigham Young University
http://faculty.cs.byu.edu/~jay
"The glory of God is Intelligence" - D&C 93