This would probably sound like rambling but that's only because I am struggling a
little bit. I implemented a little language that offers its own compound data
type: first class and users can extend it in various ways. Naturally, it is
implemented as a Racket struct. As I started using the language, it occurred to
me that I lost something and I'd very much like to get it back.
Racket struct offers some truly powerful machinery that permeates Racket
ecosystem. Here's a motivating example: having a new fancy first class compound
datatype (tm) is nice and well but what if I want it to double as a
synchronizable event? Oops. I do facilitate extensions, but that's something that
would need prop:evt on the underlying struct. I could "extend" my language and
add this prop myself, but it isn't a given that every instance needs to be an
event, not to mention there isn't "one size fits all" here, and the user may want
to customize the result of synchronization, if they even want events at all. More
generally though, how about other properties that may not even exist yet? Of
course I could surgically extend my implementation and allow to customize those
extensions etc. But that kind of opens pandora's box, not to mention most of the
time it'll simply be a "passthrough" of what Racket structs can already do, and
all of this nonsense would have to be documented - again why bother given the
marvel that is Racket documentation?
Conventional wisdom holds that you don't expose implementation details, but
honestly I'm ok dispensing with the dogma in this case. It isn't obvious to me how
to do that, though. Suppose, you derive a new stuct somehow: say, it implements
prop:evt but must otherwise be like your datatype. What does that mean? Struct
inheritance isn't that - I know that much. It must be a protocol of some kind - a
set of functions and what not (behaviors, really) that make your fancy datatype
what it is. One possible solution is Racket generics that is assuming we can
capture the essence of our type as a set of methods. Suppose for a moment, that we
could. While the underlying implementation may have changed and become either
richer or more constrained, it should still act as our fancy datatype. Since
Racket generics don't delegate to base types, are we to demand that the user
extends the interface to the struct that is nothing but a wrapper around another
struct that already implements said interface? That's asking too much IMO.
Is the answer to offer a macro that expands into something like
(struct extended-type fancy-type () #:methods gen:fancy-iface ...)
where I suspect fancy-iface methods don't need to change at all between macro
invocations?
This can't be a new problem. Any thoughts or advice?