[racket] struct-copy with parent type

37 views
Skip to first unread message

Nick Shelley

unread,
May 24, 2013, 1:18:29 PM5/24/13
to users
The struct-copy docs say "The result of struct-expr can be an instance of a sub-type of id, but the resulting copy is an immediate instance of id (not the sub-type)." Why is this?

For instance, I would hope this would work:

(struct posn (x y))
(struct 3d-posn posn (z))
(3d-posn-z (struct-copy posn (3d-posn 1 2 3) [x 5]))

My actual use case is that I'm representing some data with structs. I have the common data in a parent struct and the specific data in the child structs. One of the common fields is a unique id (a number I just increment). I'd like to be able to copy a piece of data and just change the unique id in the struct-copy. Instead I have to have a cond or a match that does the same struct-copy but with different struct ids for each sub type.

Is there an easier way to do what I'm trying to do?

Jay McCarthy

unread,
May 24, 2013, 3:16:19 PM5/24/13
to Nick Shelley, users
On Fri, May 24, 2013 at 11:18 AM, Nick Shelley <nickms...@gmail.com> wrote:
> The struct-copy docs say "The result of struct-expr can be an instance of a
> sub-type of id, but the resulting copy is an immediate instance of id (not
> the sub-type)." Why is this?

The technical reason this is the case is that in (struct-copy <struct>
... ...) the <struct> binding describes the fields of the structure
and gives access to the constructor. Thus, in your example code, you
are saying "copy this posn" and not "copy this thing and modify the
posn pieces". You could imagine that we could create a link between
parent and sub-structures, but it would be messy and imperative, in my
mind.

Alternatively, you could make a version that lists the possible
children directly:

https://github.com/jeapostrophe/exp/blob/cf6822f41c7637d9074638d0e9b4d4d2d7d27d7b/struct-copy-ish.rkt

Alternatively, you could use generics

> For instance, I would hope this would work:
>
> (struct posn (x y))
> (struct 3d-posn posn (z))
> (3d-posn-z (struct-copy posn (3d-posn 1 2 3) [x 5]))
>
> My actual use case is that I'm representing some data with structs. I have
> the common data in a parent struct and the specific data in the child
> structs. One of the common fields is a unique id (a number I just
> increment). I'd like to be able to copy a piece of data and just change the
> unique id in the struct-copy. Instead I have to have a cond or a match that
> does the same struct-copy but with different struct ids for each sub type.
>
> Is there an easier way to do what I'm trying to do?
>
> ____________________
> Racket Users list:
> http://lists.racket-lang.org/users
>



--
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
____________________
Racket Users list:
http://lists.racket-lang.org/users

Nick Shelley

unread,
May 24, 2013, 3:37:22 PM5/24/13
to Jay McCarthy, users
Thanks Jay.

So with the generics approach I would just create a gen-struct-copy with the same signature as struct-copy, then the implementation of gen-struct-copy in each of my structs would just call struct-copy with the correct struct-id. Is that the right approach?

Also, is there a way to introspect the struct-id if the struct is transparent? Something like:

(define p (3posn 1 2 3))
(struct-copy (struct-id p) p [x #:parent posn 5])

This obviously wouldn't work if p was a posn, but in my use case, the parent class is abstract so I know I'm getting child classes. I scanned through some of the struct docs, but couldn't find a way to introspect a struct type.

Matthias Felleisen

unread,
May 24, 2013, 4:40:22 PM5/24/13
to Jay McCarthy, users

Jay, should this code go into the struct-copy module? I had to write a struct-open macro recently, and I think I should add it too.

Jay McCarthy

unread,
May 27, 2013, 1:34:15 PM5/27/13
to Nick Shelley, users
On Fri, May 24, 2013 at 1:37 PM, Nick Shelley <nickms...@gmail.com> wrote:
> Thanks Jay.
>
> So with the generics approach I would just create a gen-struct-copy with the
> same signature as struct-copy, then the implementation of gen-struct-copy in
> each of my structs would just call struct-copy with the correct struct-id.
> Is that the right approach?

A generic can't be a macro, so really you'd have some explicit like a
"posn-update-x" function in the generic.

> Also, is there a way to introspect the struct-id if the struct is
> transparent? Something like:
>
> (define p (3posn 1 2 3))
> (struct-copy (struct-id p) p [x #:parent posn 5])
>
> This obviously wouldn't work if p was a posn, but in my use case, the parent
> class is abstract so I know I'm getting child classes. I scanned through
> some of the struct docs, but couldn't find a way to introspect a struct
> type.

It's possible to do something like what you want, but I wouldn't
recommend it because there's no way to recover the names of the fields
at runtime. You can just know how many there are. For example:

Jay McCarthy

unread,
May 27, 2013, 1:34:36 PM5/27/13
to Nick Shelley, users
The example got cut off:

#lang racket/base
(require racket/list)

(struct posn (x y) #:transparent)
(struct 3posn posn (z) #:transparent #:mutable)

(define (dynamic-duplicate s)
(define-values (s-type skipped?) (struct-info s))
(when (or (not s-type) skipped?)
(error 'dynamic-duplicate
"Can't duplicate... structure not open"))

(define ctor (struct-type-make-constructor s-type))

(apply ctor
(reverse
(let loop ([t s-type] [skipped? skipped?])
(cond
[(and t (not skipped?))
(define-values (name this-count _auto
this-ref mutator immutable-idxs
super super-skipped?)
(struct-type-info t))
(append (for/list ([i (in-range 0 this-count)])
(this-ref s (- (sub1 this-count) i)))
(loop super super-skipped?))]
[(and (not t) (not skipped?))
empty]
[else
(error 'dynamic-duplicate
"Can't duplicate... parent structure not open")])))))

(module+ test
(require rackunit)
(define p1 (3posn 1 2 3))
(define p2 (dynamic-duplicate p1))

(set-3posn-z! p1 4)

(check-equal? (posn-x p1) 1)
(check-equal? (posn-y p1) 2)
(check-equal? (3posn-z p1) 4)

(check-equal? (posn-x p2) 1)
(check-equal? (posn-y p2) 2)
(check-equal? (3posn-z p2) 3))

Jay McCarthy

unread,
May 27, 2013, 1:43:43 PM5/27/13
to Matthias Felleisen, users
This exact code couldn't go in, because struct-copy is written in the
kernel language, so bringing in syntax-parse and match is not allowed.
The definition is about 150 lines because of all the different error
checks and the pain of #%kernel.

Furthermore... this code has some problems like not rebinding all the
fields simultaneously, which struct-copy does.

BUT the idea of a new racket/struct module with improved version of
macros like this would be nice in my mind. In particular, it might be
a nice place for a super-struct like [1] macro that gives you keyword
field names, control over the names of predicates, etc.

What does your struct-open do?

1. https://github.com/jeapostrophe/exp/blob/master/sstruct-tests.rkt
Reply all
Reply to author
Forward
0 new messages