He points out how, given two types C is a subtype of B, meaning that a C can be used anywhere a B was originally expected, then Collection[C] is not a subtype of Collection[B]. There are theoretical principles under which you might think it should be, but practical difficulties.
The problems arise, Mark points out, when aliasing is allowed, something that doesn't occur in pure functional languages or type theory. However, another "leg" is the fact that objects can be replaced with other objects. Again that doesn't happen in functional languages. Replacement of elements is a principle of "containers" which is why he noticed it there.
However, any r/w or lvalue access, when combined with aliasing, gives rise to this problem. In fact, Perl 6 literally explains "Item containers" as a collection of one. Perl 6 is supposed to provide for optional strong typing. In the vestigial typing of Perl 5 this type violation is not a big deal, since decorating the declaration doesn't promise much. But in Perl 6, a type is supposed to mean something. Here is an example:So what would happen in C++, a mature language that demands accurate type information?sub field_promotion (Soldier $x is rw) { $x = Private.new; # Private "isa" Soldier, allowed. } my General $y .= new; field_promotion ($y); # General "isa" Soldier, allowed. $y.order_attack();
Well, we need reference semantics, not value semantics. That is the variables can refer to different polymorphic instances.void field_promotion (Soldier& x) { x = Private(); // value assignment slices object (if allowed at a +ll) }
Well, C++ rules have it that if C is a subtype of B, you can substitute C* for B* or C& for B& (giving normal use of polymorphism), but does not allow C** for B** etc. So perhaps the "is rw" is the moral equivalent of another level of indirection, and similar rules should apply. Well, how about:void field_promotion (Soldier* &x) { x= new Private(); // allowed, and updates caller's pointer variab +le } void caller() { General* y= new General(); field_promotion (y); // compile-time error }
In C++, you can't get that to happen with a regular function because the choice of overloading and type matching is done with the declared compile-time type. If you make it a virtual function, you see that the this will refer directly to the object itself, and does not alias the caller's variable. Changing the original variable does not affect this.our Soldier $x; multi sub trythisone (General $y) { # $y is a read-only alias of the caller's variable, global $x in this + case. # This function chosen based on actual type of $x at the time the cal +l was made. $x = Private.new; # what just happened to $y? $y.order_attack(); } $x = General.new; trythisone ($x);
The analogous thing in Perl 6 would be to make $y refer directly to the object that $x held at the time the call was made, so assigning to $x does not affect $y. Note that calling mutating methods on $x will affect the same object seen by $y. It's just the lvalueness of $x that is not captured. We are not aliasing the "collection of one" that can have cells replaced.
With that interpretation, the more bizzare cases involving closures to access local variables of the caller also go away.
So that's my meditation: that the "pillars of the problem" more fundamentally refer to lvalueness, not "Collection". And existing mature languages knew that all along.
—John
In reply to Subtypes and Polymorphism by John M. Dlugosz
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |