[racket-users] struct-copy question

161 views
Skip to first unread message

Kevin Forchione

unread,
Mar 16, 2018, 5:38:53 PM3/16/18
to racket-users@googlegroups.com List
Hi guys,
I’ve noticed that struct-copy doesn’t appear to work when fields are defined with #:auto. So this leads to my question, which may not be answerable since it would presumably be used to fix struct-copy, but is there a way to retrieve a list of struct fields for a struct? I have a feeling there is some macro work going on and that the struct isn’t really “aware” of its field names. If that’s the case, wouldn’t it be interesting if the macro included building a function that would return these names as a symbols list? :)

Thanks,
Kevin

Kevin Forchione

unread,
Mar 16, 2018, 5:56:31 PM3/16/18
to racket-users@googlegroups.com List


> On Mar 16, 2018, at 2:38 PM, Kevin Forchione <lys...@gmail.com> wrote:
>
> Hi guys,
> I’ve noticed that struct-copy doesn’t appear to work when fields are defined with #:auto. So this leads to my question, which may not be answerable since it would presumably be used to fix struct-copy, but is there a way to retrieve a list of struct fields for a struct? I have a feeling there is some macro work going on and that the struct isn’t really “aware” of its field names. If that’s the case, wouldn’t it be interesting if the macro included building a function that would return these names as a symbols list? :)

Actually, what wold be interesting would be a list of lists, containing the getters and setters for the struct. :)

Kevin

Eric Griffis

unread,
Mar 17, 2018, 12:24:51 PM3/17/18
to Kevin Forchione, racket-users@googlegroups.com List
How about a list of identifiers bound to getters or setters? The `extract-struct-info` procedure in Section 5.7 of the Racket Reference appears to give you that.

Eric


--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Kevin Forchione

unread,
Mar 17, 2018, 11:15:21 PM3/17/18
to Eric Griffis, racket-users@googlegroups.com List


> On Mar 17, 2018, at 9:24 AM, Eric Griffis <ded...@gmail.com> wrote:
>
> How about a list of identifiers bound to getters or setters? The `extract-struct-info` procedure in Section 5.7 of the Racket Reference appears to give you that.
>
> Eric

Souned promising, but it sounds like you have to roll a struct-info first, unless I’ve missed something. I did have some fun exploring 5.2 Creating Structure Types (the link to Examples for make-struct-field-accessor & make-struct-field-mutaor appear to lead you back to the top of the page, but as far as I can tell there are no examples there for those functions…)

No, what I was hoping for was something you could throw a structure instance at and obtains a list of its accessors/mutators so that I could then write a generalized version of struct-copy (which appears not to copy a struct that has #:auto fields. Placing a struct inside of a define though gives a pretty quick glimpse at the underlying code that the (struct ….) generates, and the mutator created by make-struct-type is simply thrown away (when not used by make-struct-mutator. I would think that between that (if it could be retained) and the struct->list function, which produces an index-ordered list of the struct’s values, that you could create a struct-copy that would populate all of the fields. Of course, you’d have a mutator out in the wild that could always modify the struct… no, you’d have to encapsulate all that into a special function foo-copy for struct foo, and pass that out with the mutator safely encapsulated in a lambda, I supposed

Kevin

Eric Griffis

unread,
Mar 19, 2018, 3:59:39 PM3/19/18
to Kevin Forchione, racket-users@googlegroups.com List
Does this help?

```
(define-syntax (dump-struct-info stx)
  (syntax-case stx ()
    [(_ info)
     (pretty-write
      `(GOT ,(extract-struct-info (syntax-local-value #'info (λ () #f)))))
     #'(void)]))
```

Then we can do:

```
> (struct point (x y [z #:auto #:mutable]) #:transparent)
> (dump-struct-info point)
(GOT
 (#<syntax::30784 struct:point>
  #<syntax::30784 point>
  #<syntax::30784 point?>
  (#<syntax::30784 point-z> #<syntax::30784 point-y> #<syntax::30784 point-x>)
  (#<syntax::30784 set-point-z!> #f #f)
  #t))
```

Eric

Alexis King

unread,
Mar 19, 2018, 5:35:35 PM3/19/18
to Kevin Forchione, Eric Griffis, racket-users@googlegroups.com List
I’m late to this thread, but perhaps I can clarify some things that I
don’t think have been made entirely clear.

First of all, you are absolutely correct that structures, at runtime,
know nothing whatsoever about field names. At runtime, structures are
fancy vectors; the names provided for their fields just end up being
used to generate accessor functions that look up values at the
appropriate index.

So how do macros like match and struct-copy know what to do? Well, the
struct macro creates a transformer binding that holds static information
about the structure type, including the names of field accessors. As
Eric already demonstrated, you can access that information using
syntax-local-value combined with extract-struct-info, but that just
produces a list containing an assortment of information.

There is a library that provides a struct-id syntax class that makes it
easier to consume structure type transformer bindings:

http://docs.racket-lang.org/syntax-classes/index.html#%28form._%28%28lib._syntax%2Fparse%2Fclass%2Fstruct-id..rkt%29._struct-id%29%29

It works with syntax/parse, and it defines attributes with names that
handle the different sorts of things that can be produced by
extract-struct-info. Disclaimer: I am the author of the library.

Finally, I am not sure about the particular issue with struct-copy and
#:auto fields (which sounds fixable), but I think it is worth mentioning
that struct-copy is irreparably broken and cannot be fixed without
fundamental changes to Racket’s struct system. Namely, it has the
“slicing” problem familiar to C++ programmers when using struct
inheritance, and the way it synthesizes field accessors from the
provided field names is unhygienic and can be easily thwarted. See this
GitHub issue for more details:

https://github.com/racket/racket/issues/1399

Alexis

Kevin Forchione

unread,
Mar 20, 2018, 12:21:25 PM3/20/18
to Alexis King, Eric Griffis, racket-users@googlegroups.com List
Thanks, guys! This continues to be a very instructive exploration for me. The restructuring of struct (lol) sounds like something for a Racket 2? The issue I have with struct-copy is pretty easy to create: 

>(struct fish (color (weight #:auto)) #:transparent)
>(define marlin (fish 'orange-and-white 11))
>(define dory (struct-copy fish marlin [color 'blue]))

 fish: arity mismatch;
 the expected number of arguments does not match the given number
  expected: 1
  given: 2
  arguments…:

Kevin

Kevin Forchione

unread,
Mar 20, 2018, 1:12:05 PM3/20/18
to Alexis King, Eric Griffis, racket-users@googlegroups.com List
On Mar 20, 2018, at 9:21 AM, Kevin Forchione <lys...@gmail.com> wrote:
>
>
Sorry, wrong example!

>(struct fish (color (weight #:auto)) #:transparent)
>(define marlin (fish 'orange-and-white))
>(define dory (struct-copy fish marlin [color 'blue]))

../../Applications/Racket/collects/racket/private/define-struct.rkt:953:29: fish: arity mismatch;
the expected number of arguments does not match the given number
expected: 1
given: 2
arguments...:

Kevin


Kevin Forchione

unread,
Mar 21, 2018, 12:36:19 AM3/21/18
to Alexis King, Eric Griffis, racket-users@googlegroups.com List

Just read your GitHub link and see the problem goes beyond my simple #:auto issue. Thanks for that link. I’m pretty green to all the thorny issues when it comes to dressing primitive datatypes up in fancy bindings and hope the magicians of indirection can somehow pull a rabbit out of the hat on this one someday. :)

Kevin

Eric Griffis

unread,
Mar 21, 2018, 6:32:29 PM3/21/18
to Kevin Forchione, Alexis King, racket-users@googlegroups.com List
I bump into struct subtleties all the time. For example, my attempts to #:auto always degenerate into custom constructor procedures; This usually involves #:constructor-name and #:omit-define-syntaxes, which might not be a big deal if I had a solid understanding of what these flags actually do. So far, all of my struct troubles have been rooted in a lack of understanding.

It is unfortunate that such a fundamental aspect can be so un-intuitive. Reading the source hasn't helped much because structs are a low-level construct used *everywhere* for *everything*. This would be a code smell if I didn't trust that our Racket ancestors knew what they were doing, so the notion of "fixing" structs (or even struct-copy) seems misguided.

Structs seem to occupy an awkward region of the design space between totally opaque (e.g. objects) and totally transparent (e.g. algebraic data types). On one hand, structs are too "open." The "good for everything" design makes them not particularly great at anything. Prefabs are a pathological case -- every attempt I've made at using them devolves into lists or non-prefabs in order to re-gain control over basic things like construction or printing. On the other hand, structs aren't "open" enough. Racket's pattern-based binding forms allow me to de-structure, but it's not yet clear to me how much extra plumbing would be needed for something like a totality check, or even where that plumbing would go.

I can appreciate the architectural decision that structs represent for the core -- flexibility often comes with a cost to conciseness and convenience. For something like a standard library, where transparency prevents friction between language users and implementers, I think higher-level abstractions are more appropriate. My preference is for ADTs: they support a pattern-based functional style, are amenable to static analysis and automatic tooling, and I already have an intuition for how they should behave.

/rant

Eric

Alexis King

unread,
Mar 21, 2018, 7:43:49 PM3/21/18
to Eric Griffis, Kevin Forchione, racket-users@googlegroups.com List
> On Mar 21, 2018, at 15:32, Eric Griffis <ded...@gmail.com> wrote:
>
> This would be a code smell if I didn't trust that our Racket ancestors
> knew what they were doing, so the notion of "fixing" structs (or even
> struct-copy) seems misguided.
>
> [snip]
>
> I can appreciate the architectural decision that structs represent for
> the core -- flexibility often comes with a cost to conciseness and
> convenience.

Without necessarily responding to all of your message, I think that it
would be an exaggeration to state that structs are the way they are
because they need to or ought to be that way. I was by no means using
Racket when most of the features that exist in the current struct system
were designed and implemented, but I believe I have heard or read it
said that certain choices are well-known to have been the wrong ones in
retrospect (#:transparent not being the default, for example).
Unfortunately, as you yourself say, they are used to represent nearly
everything in Racket, and redesigning them in a backwards-compatible way
is a daunting task.

On the one hand, I think Racket’s structures are fundamentally
challenging because they must accommodate dozens of use cases that are
necessary for Racket-the-platform but probably not directly relevant to
the majority of Racket programmers. For users, they would often like
them to be boring, plain old data types, but all sorts of other systems
need to be in place for other things to work. For example, they must be
chaperoneable/impersonatable in order for the contract system to work,
structure type properties exist to facilitate some sort of low-level
generic protocol mechanism, and structure inspectors allow privileged
tools to inspect the structure of “opaque” structures (though I’m not
actually sure what depends on this feature... DrRacket? ...the compiler
itself?).

On the other hand, certain things seem almost certainly incidentally
complex, like how the way the default printer prints structures is tied
to the structure’s inspector and how #:auto works. Generally, I think
#:auto is better avoided and replaced with your own wrapping
constructor, possibly using #:constructor-name to adjust the underlying
constructor name, but #:auto can’t be removed because Racket tries
pretty hard to never break backwards compatibility.

Alexis

P.S. Every so often someone kicks around the idea of “maybe we’ll fix
these things in a #lang racket2”, but that’s really just wishful
thinking at this point, given that I don’t believe anyone is working or
planning to work on implementing such a thing. Doing so is not trivial,
either, given that the two systems would need to be able to interoperate
to some extent.

Eric Griffis

unread,
Mar 21, 2018, 9:17:38 PM3/21/18
to Alexis King, Kevin Forchione, racket-users@googlegroups.com List
On Wed, Mar 21, 2018 at 4:43 PM Alexis King <lexi....@gmail.com> wrote:
> On Mar 21, 2018, at 15:32, Eric Griffis <ded...@gmail.com> wrote:
>
> This would be a code smell if I didn't trust that our Racket ancestors
> knew what they were doing, so the notion of "fixing" structs (or even
> struct-copy) seems misguided.
>
> [snip]
>
> I can appreciate the architectural decision that structs represent for
> the core -- flexibility often comes with a cost to conciseness and
> convenience.

Without necessarily responding to all of your message, I think that it
would be an exaggeration to state that structs are the way they are
because they need to or ought to be that way.

Thank you for the clarification. I meant to imply that Racket devs were capable of making sensible implementation choices at their given level of detail, because otherwise I wouldn't be using Racket today.
  
On the one hand, I think Racket’s structures are fundamentally
challenging because they must accommodate dozens of use cases that are
necessary for Racket-the-platform but probably not directly relevant to
the majority of Racket programmers. For users, they would often like
them to be boring, plain old data types, but all sorts of other systems
need to be in place for other things to work. [...]

On the other hand, certain things seem almost certainly incidentally complex [...]

This is why I am advocating an additional layer of abstraction to provide platform support and custom syntax for general purpose computing with composite data. An "algebraic data structures" library seems like a good place to encapsulate struct arcana safely. There are some interesting fragments in the package repository. It would be interesting to see a complete picture.

Eric
Reply all
Reply to author
Forward
0 new messages