-
Notifications
You must be signed in to change notification settings - Fork 205
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
[extension-types, views] Allow variable show #1648
Comments
Nitpickery: A class cannot declare a member with the same name as itself, but it can inherit one, so: class X {
int get Y => 42;
}
class Y extends X {}
view Z on Y show Y {} // means show the member or the interface? If you can write I'd like to be able to I'm not sure allowing you to hide/show individual members is really where we should put our complexity budget. view Foo<X> on X {...} // shows nothing, declared members only.
view Foo<X> on X show * {...} // Exposes all members of `X` except where overridden by declaration.
view Foo<X> on X hide substring {...} // same as `show * hide substring` Since views are inherently static (no run-time reification!), we can allow the signature of foo<T extends num>(T value) =>
Foo<T> foo = value;
foo.exposedMemberOfNum();
} I think it can work. |
@lrhn wrote:
The proposals specify that a show/hide identifier which is a type as well as the name of a member of the interface of the on-type (as known in the declaration of the view/extension_type, i.e., based on the bound if the on-type is a type variable), it denotes the member. But that's probably not a very important name clash, because members typically have names whose first letter is lower-case.
It sounds like you want to specify that a given member class A { void foo() {} }
class B extends A { void foo([_]) {} }
view V on B show A.foo {}
void main() {
V v = B();
v.foo(); // OK.
v.foo(true); // Error, the signature is `void foo()` (as in `A`, not `void foo([dynamic])` as in `B`).
} The current proposal does not allow the show/hide part to change the signature of a member, it's always using the signature of the member in the interface of the on-type. So the show/hide part is only enabling or disabling the members of the on-type interface, it is not changing them in any way. Do you have an example where it's helpful to be able to specify that the signature should be taken from a superinterface rather than from the on-type? (Or, in general, that the signature is any other signature than the one from the on-type?)
foo<T extends num>(T value) {
Foo<T> foo = value;
foo.exposedMemberOfNum();
} Yes, that's exactly what I intended!
Sounds good! |
The main motivation for views/extension_types (I'll just say 'views' now) is to provide a zero-cost abstraction (that is: no wrapper object) that allows developers to access a given underlying object using a different interface. So we have a representation object The Using For instance, if we wish to use an We could of course achieve a similar effect by wrapping the class IdNumber {
int _idNumber;
IdNumber(this._idNumber);
operator ==(other) => other is IdNumber && other._idNumber = _idNumber;
hashCode => _idNumber.hashCode;
} .. but using a view saves both space and time because there is no such wrapper object: view IdNumber on int {} // Members of `Object` are shown by default.
That's true. The view is not a security mechanism, it's very easy to break any level of protection that views could be considered to provide: If the view is intended to protect a mutable representation from "unauthorized" mutation then we can just keep a separate reference to the on-object But I believe that views can still be useful: They will help a group of developers maintain restrictions that they wish to maintain. They will know that it isn't a good idea to keep a reference to a mutable object which is accessed using a view and mutating it directly, and I'm sure we will be able to develop programming patterns where such things rarely happen.
That's a fun idea, and it would of course work quite well to transfer an object from a subclass However, it is a tricky operation: You have to guarantee that there are no references in the heap or on the stack typed by the subclass |
Sounds like you think we have similar guarantees already? ;-) Note that we are talking about using aliasing to violate an invariant which is otherwise maintained by a well-defined amount of code. For instance, let's say that we have an object that contains two lists, and they must have the same length. We store those two lists in private instance variables, and make sure that every method in the class will preserve the invariant. class PairedLists<X> {
final List<X> _xs1, _xs2;
PairedList(this._xs1, this._xs2): assert(_xs1.length == _xs2.length);
void add(X x1, X x2) { _xs1.add(x1); _xs2.add(x2); }
...
} But it's obviously easy to break that invariant. Client code could keep an alias to one of those lists and use it to break the invariant: void main() {
var xs1 = [1];
var pl = PairedList(xs1, ['a']);
xs1.add(2); // Invariant broken!
} We can of course add aliasing control to Dart (for instance: ownership types), and we could also add a bunch of rules like "the first argument to this constructor must be created when it is passed as an actual argument, it can't be a pre-existing object". It's a huge project, of course, but it is not unthinkable that Dart could have statically safe aliasing control. However, I don't think that the need to maintain invariants will go away, and I also don't think that it is realistic (or even useful) to insist that we can only support invariant maintenance if there are no loopholes. Views have two loopholes in particular: You can access the underlying on-type instance via a downcast, and you can keep an alias to the underlying on-type instance. In both cases, you can use the interface of the on-type instance with no restrictions, and this may allow us to break invariants that are otherwise maintained by the view. If you're willing to pay for a wrapper object (in terms of time and space) then you can eliminate the loophole with the downcast, but the loophole associated with the alias still exists. My conjecture is that it is worthwhile to support views; they have two loopholes rather than one, but they can save a lot of time and space, and they are essentially just as safe to use as a wrapper class, for developers who wish to maintain those invariants. |
That's determined by the software design: In the view proposal, the boxed and unboxed representation have types that are unrelated, so there is no way you can get it wrong according to the declarations without having compile-time errors. I even made the choice to require an explicit boxing operation, because I find it important to be aware of this step. (That's a controversial part of the proposal, so we may end up having implicit boxing after all, or user-specified support/non-support for implicit boxing.) It is up to the designer of the software that contains usages of the view type and/or the associated boxed type to make the choice. I find it likely that the view type (hence, the unboxed representation) will be used consistently whenever that is possible. No need to pay for boxing, no need to deal with the identity confusion that may arise if the same on-type instance is boxed twice. However, in the case where boxing is required (say, in a case where the object must be the value of a variable/parameter whose type is some class type that the view Note that the identity confusion that may arise when we invoke the We could canonicalize the wrapper objects, but that may well be overkill: It isn't that hard to write an extra member of any given view |
Cf. the extension types proposal and the semantically very similar views proposal.
The
show
clause in an extension type or viewFoo<X1 extends B1, .. Xk extends Bk>
enables invocations of members of the underlying on-type. For instance:We may wish to compute the members included by the
show
clause based on the on-type, and this might actually be tractable in spite of the fact that it seems to be similar to "class C<X> implements X ...
":This feature makes it possible to bundle a set of view members with any type (when
X
has no bound) or with any of the set of subtypes of a given typeT
(withFoo<X extends T> ...
), and it allows us to preserve the full interface of the on-type.It is indeed a quirky feature, but it might be quite useful.
@lrhn, @leafpetersen, @natebosch, @stereotype441, @jakemac53, @munificent, WDYT?
The text was updated successfully, but these errors were encountered: