-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Subtyping: specialised to constituent type and “fix opt” introduce inconsistencies #135
Comments
Interesting observation. I've been warming up to removing the rule. Here is the use case for having it. Imagine a configuration parameter:
Now, in some iteration of the service's life time, the configuration record is extended with a new field
At this point you actually want to be able to make
The rule allows that in a backwards-compatible way. It also is natural to have on an intuitive level. But I agree that its meta-theoretical properties are somewhat hairy, as it evokes some of the typical problems of non-disjoint unions. |
Interesting use case, thanks! You could even do the following variant: Initially, your published interface (“published interface v1”) is
Later you want to switch to
and still be compatible with all clients (those using v1 and those using v2) by implementing the interface
Distinguishing between published interface and implementend interface (where the implemented interface is a subtype of all published interfaces) is a pattern that will emerge, I expect. It is just too useful to remove “old cruft” from the documented interface without breaking clients, so people will do it. But back to the issue at hand… should we try to fix it? This might work for a set of
This might fix the (rather formalistic) inconsistency of the previous formulation. It does not fix the problem that a decoder needs to worry about |
Hm, is that enough? Couldn't it still be |
That's all equivalent, isn't it? All of these rules are written with type names already substituted (e.g. operating on infinite/coinductive rules). |
Doh, yes. I need a vacation. Okay, I can't think of any other case, but is there a |
You mean another case of inconsistency/looping rules? No really. Note that
could be rewritten as
right? |
Hm, I guess. So if we were to define some weight metric to prove termination, we could take the lexicographic order on pairs Edit: Hm, actually no, my size metric for types is broken. If |
I guess we can prove termination if want to use the fact that the type, although coinductive, is regular (is that the right term), i.e. defined by a finite graph. In actual applications it will, but I hate to appeal to that in the metatheory (in my Coq formalization of Candid earier it was nice and elegant that I could just treat the types as coinductive, and didn’t have to worry about ids, and graphs, and environments). |
@rossberg I am kinda waiting on a judgment call by you here. Should we
As you can tell from this loaded question, I am leaning towards the former. |
Hmm, here is something to consider. What if we do something like this:
This way, we have “specialize to constituent type” only when the constituent type itself isn't (This is not completely worked out yet, and transitivity might require special care with |
causes a stack overflow. also see discussion in #135
Interesting suggestion. I suppose options of options are not something you would see often in an interface, so limiting their evolution might be okay. How confident can we be that this has no problems? I wish we had spare cycles to mechanise this, and prevent further holes. Edit: For the use cases discussed up-thread, it still seems relevant to support this case. |
I don’t see anything immediately wrong with it, and it seems it avoid some of the squishy meta-theoretical problems (e.g. requiring equality on co-inductive types). So I’d be fine with going that way for now, right?
Right, but the restricted form is enough, isn't it? The use-case, in prose is “I want to allow new clients that don’t send One could argue that this is only needed if |
Yes, I agree that this supports the use case. Was a reply to your previous comment. Okay, so let's go with this. |
Let’s continue at #137 then |
I tried to prove the “weak transitive coherence” that we claim in Coq: Theorem transitive_coherence: forall ta tb tc v1, ta <: tb -> tb <: tc -> v1 :: ta -> coerce tb tc (coerce ta tb v1) [= coerce ta tc v1. where [= allows more null on the left than on the right. I believed this holds, but the proof doesn't go through. A counter example is `bool <: opt bool <: opt opt bool`. Coercing `true` in two steps goes via `opt true` to `opt opt true`. Coercing directly goes to `null`, because the “constituent-to-opt” rule `t <: opt t'` requires that `t'` is a non-opt type. We added that restriction in 30f719f for the reasons discussed in #135 (comment) This PR just updates the prose to not claim wrong things. (This is a good humbling reminder about how easy it is to go wrong when one does not do formal proofs.)
I tried to prove the “weak transitive coherence” that we claim in Coq: Theorem transitive_coherence: forall ta tb tc v1, ta <: tb -> tb <: tc -> v1 :: ta -> coerce tb tc (coerce ta tb v1) [= coerce ta tc v1. where [= allows more null on the left than on the right. I believed this holds, but the proof doesn't go through. A counter example is `bool <: opt bool <: opt opt bool`. Coercing `true` in two steps goes via `opt true` to `opt opt true`. Coercing directly goes to `null`, because the “constituent-to-opt” rule `t <: opt t'` requires that `t'` is a non-opt type. We added that restriction in 30f719f for the reasons discussed in #135 (comment) This PR just updates the prose to not claim wrong things. (This is a good humbling reminder about how easy it is to go wrong when one does not do formal proofs.)
The “option type can be specialised to its constituent type” still worries me…
Here is one problem, maybe fixable, but maybe indication that this rule just doesn't fit well with the system.
With this rule, we certainly want
But also
So what does this mean for
Let’s try to apply the rules (with a logical variable
?X
for the result).and we are in a loop. The solution
?X = opt ?X
is not allowed (our values are inductive, not coinductive – I hope!). And since we understand_ ~> _ : _
to be inductive, we should getBut by the rule about “failed parses at type
opt
turn into null`”, this implieswhich (because
opt FixOpt = Opt
) implieswhich implies
by the “subtype to constituent type” rules.
This is a blatant contradiction! This means our
_ ~> _ : _
relation is inconsistent!How can that happen? Don’t all inductively defined relations well-defined? No, they are only well-defined when the relation appears only in strictly positive positoins in the rules. And our rule
breaks that. This would also be a major headache when trying to put this into a theorem prover.
The way to deal with that there (e.g. Coq) would be to define the relation as a well-founded function on the
v
(we can’t uset
because the types themselves are coinductive). In all rules, the antecents only mention subterm ofv
. And it also (nicely) matches a real implementation which would traversev
.Well, all rules but his one:
Maybe we can patch around this issue (e.g. requiring
<t> ≠ opt <t>
) … but I am leaning towards just dropping it. It makes the formalism cleaner (a good sign) and it makes the implementation easier to get right and secure (they otherwise have to guard themselves against an infinite loop upontrue ~> ? : FixOpt
).Do we have any compelling reason/use case for this rule?
The text was updated successfully, but these errors were encountered: