-
Notifications
You must be signed in to change notification settings - Fork 98
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
Design proposal: Type canister upgrades via an "Evolves to" relation, _not_ with subtyping. #753
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't think about it in detail yet, but it's indeed a good approach to question whether the function arrow needs to be contravariant. All our impossibility results in Zurich depend on that assumption, and seeing if we can get rid of it is worthwhile. (How we call the resulting relation is less important :-))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, taking a second look now. Lot’s of prose … but I feel I can summarize the content (in a slightly unflattering way, I guess) as: “Let’s explore a relation that elaborates into conversion functions both ways and where all type constructors are covariant”.
But I miss the rules for this relation, and also for the relation on values that you propose! (Or at least some rules for simple cases, to check soundness).
BTW: The two relations form not just a galois connection, but a galois embedding, because one of the compositions is the identity, it is a galois embedding, if I remember correctly.
It's worth exploring whether we can decouple evolution from the subtyping relation. However, I don't understand how that would magically make functions co-variant. Functions very much invert the direction of time or the evolves-to relation for their arguments (i.e., a potentially older client passes arguments to a new server, which then returns new results to the older client), and this switches again in the higher-order case (an old client passes an old function to a new server, which invokes it with new arguments consumed by the old function, which then returns old results to the new server). I'd need to see the elaboration rules, i.e., how the two coercions e1 and e2 are used, especially in the higher-order case. Somehow their use has to switch direction for function arguments. I believe it if I see rules that are type-correct and where e2 does not end up being equivalent to the usual contra-variant coercion. ;) |
Ok, let’s try to come up with some simple upgradeable types. It seems because of the two functions needed, we would not have So maybe a small example to exercise it through is extending a variant type, and the simplest form is
Now assume the following method after upgrade from type
I can see what happens when the argument is passed as The question of “where” things are happening is maybe not so important, but if the answer is “the receiver of the reply”, then we definitely need some form of coherence result, i.e.g
because that code cannot know how the other party upgraded. If the answer is “the upgrading party applies these functions”, then we may get away without coherence, but (I think) only if the you record the evolution of the canister. Which has similar implications than Dom’s upgrade plan, where you need tooling to know old versions of your code. So I assume we want a coherent setup for now. The next question is: Does this work in the higher order case (which, I believe, is where non-contravariant function arrow rules frequently break down)? And to answer this, I think we need more clarity as to “when do I apply |
Can't find the discussion right now either, but it got dropped because it also wasn't correct in the higher-order case, exactly because it tried to not switch directions contra-variantly. FWIW, I'm tending towards going back to the rules that allow {a : opt T} < {} < {a : opt T}. I think the coherence issue is avoided if there is no separate transitivity rule (i.e., transitivity is admissible). That would still mean that, technically, the semantics of composing the coercions for multiple upgrades may not be coherent -- i.e., a new parameter version could accidentally pick up a very old field. But I wonder whether that matters in practice. In practice, we would recommend to not contra-variantly remove optional fields. Instead, deprecate them by turning them into |
So we’d still be able to prove
but not
Maybe that works, but it surely doesn’t improve my sleep ;-) |
@nomeata, yes exactly. My sleep is fucked up anyway. :) |
The problem is less pressing for multiple upgrades of the same, sag, argument, but rather for independent upgrades, where one side changes the return type and the other side an argument side. But maybe not even that is too pressing… But if you have Does this lead towards a world where we end up with an equivalence relation describing mutually compatible types, rather a partial order? |
Hm, not sure how that scenario can arise on the platform. Normally every canister has a clear owner, who also is the only one that can initiate an upgrade and change the type of a function in its interface. Perhaps in some higher-order case, but I can't think of anything concrete.
Sorry, you lost me there. Operationally, you can only ever go one (direct) way, which is determined by the two types involved. The compiler itself never composes coercions. |
Yes, higher order (everything would be simple in the single order case…) Canister A has
You just explained how to go from
to
because you allow both removing and addition
|
Ah, I see. No, you can't use depth subtyping that way, but that can never possibly work AFAICS. But importantly, you can use record extension deeply, e.g.:
|
I think Matthew wants to prove you wrong in #753 :-) This might work. Is it fair to rephrase your proposal as “when a field has an |
Can we close this to get it off the list of open PRs? |
Has the conversation resolved? I didn't allocate any time to this, and somehow missed the comments from last month in my github notification email blasts. I apologize. I think the possible next steps to take are:
|
Well, #832 isn’t through just yet, so I guess the conversation isn’t completely resolved. |
@nomeata is trying to distill the idea down to something about optional fields; that all sounds plausible, but I can't recall the main ideas right now, since I haven't thought about them recently. They are out of cache. I did notice that @ggreif hasn't said anything above (though he's edited some comments), or I missed it. A lot of this idea germinated from something that he said to me when we were all in the Radiohead conference room one day in October. I'll try to remember it in fuller detail by referring to my notes today. |
This conversation can permanently move to #832 The conversation there is more relevant than these notes, which we can remove now. |
This week in Zurich, the ActorScript team discussed the type discipline for canister upgrades.
We had been thinking (collectively) that we want subtyping to play a role here. I've never accepted that, and now I'm certain it's wrong. @ggreif gave me some useful hints during that discussion that have since germinated into a fuller idea than I had understood at the time. I've tried to capture it here.
Also, before leaving, @crusso has humored me with lots of helpful discussion today. Those discussions are (mostly) reflected here too, though the full set of on-paper rules are currently missing here.
I'm writing up the current status of that discussion here to memorialize it and to continue it more broadly, with @rossberg @nomeata @chenyan-dfinity @kritzcreek