-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Make classes first-class citizens #10667
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
Comments
Removed Type-Defect label. |
To a degree, classes are first class (you can pass Type objects around) but we require literal types in new. There are reasons for that: types do not and should not describe the signatures of constructors (i.e., if you implement an interface, you don't want to be forced to implement the same constructors). Languages that allow this sort of thing don't have constructors in the C++/Java tradition. Instead, they have instance methods on the class objects. In Dart, we could assume that every type object has instance methods that correspond to its constructors and static methods, and you can use those instead of new. We could do that (and I am in favor) but we have not made any decision. |
ObjectPascal has virtual constructors which are basically what you'd need here. |
Any update on this? Can we wait for this? |
All traces of the metaclass feature mentioned here ('they have instance methods on the class objects') have been removed from the language specification over time, mainly because that feature is inherently at odds with static typing. For example: class C {
static void staticMethod() {}
C.named();
}
main() {
dynamic d = C(); // We've now forgotten that this is a `C`.
Type t = d.runtimeType; // Get hold of the reified type of `d`.
// The reified type would then have an instance method for each "class method" of `C`.
t.staticMethod(); // Calls `C.staticMethod()`.
dynamic other = t.named(); // Creates an instance like `C.named()`.
} Let's call the static methods and constructors of a class The static interfaces are islands in the type system, in the sense that there is no subtype relationship between the static interface of a given class class C ... // Same as before.
class D implements C { // Or `extends`, that does not matter.
static int completelyDifferentStaticMethod(double d) => 42;
D.otherName();
}
main() {
C c = D.otherName(); // We don't know statically that `c` is a `D`.
if (...) c = C.named(); // .. and, in general, we can't know such things.
Type<C> t = c.runtimeType; // Assuming a generic `Type`.
t.staticMethod(); // Works for `C`, not for `D`. Hence: Not safe.
} We could change However, that wouldn't even help us here, because the static interface of So the metaclass concept certainly does not fit well into a statically typed setting, unless we change the requirements on static interfaces radically. But it would be massively, massively breaking to start forcing the static interface of all classes to be a subtype of that of its superinterfaces. It probably wouldn't work well in practice either, because it's just not obvious that you want your class Further discussion on this topic could go in some other direction, but I believe it's a safe bet that Dart will not have metaclasses (in the sense that |
It would certainly be breaking, but I don't think it need be massively breaking. |
We could allow an instance class C {
static staticMethod() {}
C(int i);
}
class D implements C {
D.name(double d, double d);
}
main() {
Type<C> t = D;
t.staticMethod(); // Calls `C.staticMethod()`.
} This basically means that every type will inherit all the static methods of all its supertypes. There will probably be some name clashes to sort out, but that might work. The difficult part, I suppose, would be to ensure that every type has all the constructor signatures of all its supertypes. So if we have the constructors shown above then we must be able to construct a main() {
Type<C> t = D;
C c = t(42); // We do have a constructor `C(int)`, but we must create a `D`.
} At least, it seems wrong to me if a supposed constructor invocation on a value of type If you don't have that, how would you ensure that metaclass construction is type safe? |
In your description, everything is assumed to be virtual. I'm saying, we don't have to assume that.
That gets us to where we are today. Then you could opt-in to exposing these static methods and constructors: class C {
virtual C(int i) { print('C($i)'); }
virtual C.name(String s) { print('C.name($s)'); }
virtual static staticMethod() { print('C.staticMethod()'); }
virtual static staticMethod2() { print('C.staticMethod2()'); }
}
class D extends C {
D.name(String s) : super(s) { print('D.name($s)'); }
static staticMethod2() { super.staticMethod2(); print('D.staticMethod2()'); }
}
class E implements C {
E(int i) { print('E($i)'); }
D.name(String s) : { print('E.name($s)'); }
// compiler error: E does not implement C.staticMethod()
static staticMethod2() { print('E.staticMethod2()'); }
}
main() {
Type<C> t = D;
t.staticMethod(); // prints "C.staticMethod()"
t.staticMethod2(); // prints "C.staticMethod2()" then "D.staticMethod2()"
print(t(1).runtimeType); // prints "C(1)" then "D"
print(t.name('x').runtimeType); // prints "C(x)" then "D(x)" then "D"
t = E;
t.staticMethod2(); // prints "E.staticMethod2()"
print(t(1).runtimeType); // prints "E(1)" then "E"
print(t.name('x').runtimeType); // prints "E(x)" then "E"
} Or if you want to get really fancy: class C {
virtual static staticMethod() { printMe(); }
virtual static printMe() { print('I am C'); }
}
class D extends C {
static printMe() { print('I am D'); }
}
main() {
C.staticMethod(); // prints "I am C"
D.staticMethod(); // prints "I am D"
Type<C> t = D;
t.staticMethod(); // prints "I am D"
} You could also have (Side note, I wish we had a |
It's an interesting topic! I think the pieces could fit together with a radical model (if we have "everything" at the meta-level), but it will probably not be easy to achieve, for instance, type safety if we have less than that. @Hixie wrote:
Actually, I'm just assuming that if In other words, we could have support for both virtual (that is: normal) static methods and non-virtual static methods (that is, methods that can not be overridden), but all of them should at least be inherited. I think the most reasonable and powerful approach would be to say that we are talking about instance methods on class objects, that is, methods declared in metaclasses. This then means that static methods and constructors are regular methods on a class object (i.e., on an instance of a metaclass), which again means that all the normal rules should apply at the meta-level. This would give us a static analysis and a semantics for invocations, for tear-offs, for superinvocations, for "everything", which would otherwise be a long list of newly invented rules.
I think this implies that I do think that any developer who has experience with Dart or any other typed OO language would be justified in expecting inheritance to be supported, even for methods that can not be overridden. So it's at best surprising. Apart from that, of course, it's not type safe: With an expression of static type |
That's already true: C.staticMethod(); // works
var x = C;
x.staticMethod(); // fails I'm just saying that we should continue doing that, but add the concept of inherited/virtual methods to these metaclasses.
It's supported, just has to be explicitly opted-into. An alternative approach would be to use the static type for dispatch to non-virtual static methods: class C {
static staticMethod() { print('C.staticMethod()'); }
virtual static virtualStaticMethod() { print('C.virtualStaticMethod()'); }
}
class D extends C {
static staticMethod() { print('D.staticMethod()'); }
static virtualStaticMethod() { print('D.virtualStaticMethod()'); }
}
class E implements C {
static virtualStaticMethod() { print('E.virtualStaticMethod()'); }
}
main() {
C.staticMethod(); // prints "C.staticMethod()"
D.staticMethod(); // prints "D.staticMethod()"
// there is no E.staticMethod()
Type<C> t = D;
t.staticMethod(); // prints "C.staticMethod()" - note, NOT D.staticMethod()
t.virtualStaticMethod(); // prints "D.virtualStaticMethod()"
t = E;
t.staticMethod(); // prints "C.staticMethod()"
t.virtualStaticMethod(); // prints "E.virtualStaticMethod()"
} That would be consistent with e.g. what ObjectPascal does. I'm not sure I can think of another language that has static typing and metaclasses. |
Cf. a related issue in the language repo: dart-lang/language#356, including this comment, which goes deeper into a potentially useful emulation of virtual static methods: Use a companion object to the class. |
This issue was originally filed by [email protected]
It would be nice if classes could be passed around as objects the same way closures/functions are, e.g.:
void foo() => print("bar");
var baz = foo;
baz();
This works fine. For classes, however, this
class Foo {
}
var Bar = Foo;
var baz = new Bar();
fails with "using 'Bar' in this context is invalid".
The text was updated successfully, but these errors were encountered: