I have a project with 57 prefab structure types. I need to
construct instances using a local contract (module level
contracts do not fit my needs here). Since I cannot define guards,
the solution is easy enough.
Problem: I already have a few hundred constructor calls without
contracts. I could either A) rewrite them all to use contracted
constructors, or B) attach local contracts in a sweet spot so that
I don't have to rewrite anything else.
I prefer option B, but it doesn't look like I can attach a local
contract to a constructor with `struct` alone, or even with an
impersonator. When I hack around to rebind or hide the
constructor's identifier, I break compatibility with `match` and
`defstruct*`.
If you were in my position, what would you do?
~slg
--
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/0a16cfbe-4789-a939-796e-5f6f9da21626%40sagegerard.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CANy33qmngGoVoAok6%2BR885jkh8MroMqYHpOd6XtjCSH7iiESQA%40mail.gmail.com.
Of course, if you're okay with a longer email. Before that, thank
you both for volunteering your time to code something out. I
enjoyed running into a `define-module-boundary-contract` in the
wild for the first time.
I sometimes print output in a (read)able form because I like analyzing my logs. The data I print includes prefab structures, with type ids matching the topic they cover or the statements they make. You can see that I declare prefab structure (sub)types cutely labeled "messages" here. [1]
The idea is that I can either print (for example) a $package:output:built instance as a localized string, or just toss the instance itself into writeln. When I read instances back from a port, the struct accessors will help me filter and match. Hopefully all this shows where my head is.
Now for the problem. Look at [2], but don't worry about what it
means. I just wanted you to see the constructor call in the
exception handler. If I made a mistake and wrote that line such
the exception was placed directly in the instance, then I wouldn't
be able to (read) that instance back later! I cannot allow
#<...> forms in my output, or some symbol soup that happens
to be readable, but doesn't constitute the value it used to be.
TL;DR I want to protect the invariant `(equal?
V (read (open-input-string (~s V))))` for each V I
print to an output port.
Finally, as to why I didn't want the module boundary contract. The module that declares prefab structure types is also primarily responsible for creating all instances of those types. I rarely cross module boundaries when applying the constructors.
[1]:
https://github.com/zyrolasting/xiden/blob/master/package.rkt#L49
[2]:
https://github.com/zyrolasting/xiden/blob/master/security.rkt#L100
Here's another minimally-tested sample implementation. A more robust solution might try to chaperone the struct type, as well, to protect reflective access to the constructor—but I wonder if that really makes sense when you are working with prefab structs. If you can explain more about your requirements, it might be possible to suggest better approaches.
On Sun, May 9, 2021 at 7:57 PM Ryan Culpepper <rmculp...@gmail.com> wrote:
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CANy33qmngGoVoAok6%2BR885jkh8MroMqYHpOd6XtjCSH7iiESQA%40mail.gmail.com.
-- ~slg
Almost forgot, just in case someone asks: I want to avoid
checking for invariant violations when I print. That would entail
checking a bunch of values in accumulated program output, where it
would be awkward to do something non-printing related, let alone
raise an error. When I am printing logs, all invariant
violations come down to what went a constructor, because all
program output is encoded as prefab structures. That's why I want
to raise any errors on instantiation.
-- ~slg
I hope so! That's what I understood option B to mean.
I could adjust the define-message macro according to the info in
Ryan and Phillip's attachments, but I'm not well-educated on
trapping struct operations. Phillip: Your example and email gives
me the impression that when you add a chaperone, you can proxy all
struct operations, including the gap left by the chaperone for the
constructor. Is that true?
The macro I'd want has to preserve all characteristics of all generated struct bindings, which makes things tricky since the options for `struct` normally operate on a strict subset. e.g. #:[extra-]constructor-name seems to operate on the constructor alone, and the #:extra- variant does not preserve the match-expander/super-id characteristics. #:constructor-name is closer, but the original constructor id is no longer directly callable by the declaring module.
. To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/20210510110509.cpg6jnil6th7xbsi%40topoi.pooq.com.
-- ~slg
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/94e81fc9-e8a1-e677-bf0e-b49e597313b9%40sagegerard.com.