-
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
Abstract static methods #356
Comments
Shouldn't |
In my comprehension, it won't work since the goal is to instantiate any class that implements |
You are right, my fault. But I think I know understand why it doesn't work: If you print the type parameter it will tell you that it were of the type you specified. However, when you print the field |
Cf. an old SDK issue on a similar topic: dart-lang/sdk#10667 (search for 'virtual static' to see some connections). This doesn't fit well in Dart. The main point would be that Rust has a different approach to subtyping,
as stated here. @HugoKempfer wrote:
Right; even if In general, constructor invocations and static method invocations are resolved statically, and any attempt to invoke them with an instance of You might say that it "should work", and we did have a proposal for adding such a feature to Dart for quite a while, with some preparation for it in the language specification. But every trace of that has been eliminated from the spec today. The issue is that, in Dart, this feature conflicts with static type safety: There is no notion of a subtype relationship between the static members and constructors of any given class type and those of a subtype thereof: class A {
A(int i);
static void foo() {}
}
class B implements A {
B();
}
void bar<X extends A>() {
var x = X(42); // Fails if `X` is `B`: no constructor of `B` accepts an int.
X.foo(); // Fails if `X` is `B`: there is no static method `B.foo`.
} It would be a hugely breaking change to require every Dart class to implement constructors with the same signature as that of all of its supertypes, and similarly for static methods. Already the signature conflicts could be difficult to handle: class A { A([int i]); }
class B { B({String s = "Hello!"}); }
class C implements A, B {
// Cannot declare a constructor which will accept an optional positional `int`,
// and also accepts a named `String` argument.
C(... ? ...);
} Apart from conflicts, it's not desirable. For instance, The situation is quite different in Rust. I'm no Rust expert, but we do have the following salient points: The declaration of So we wouldn't want to add anything to Dart which is directly modeled on the ability in Rust to require that all subtypes have a static interface that satisfies the usual override rules. But it's worth noting that this "static interface" of a Rust trait is similar to the instance members of a separate object, somewhat like the companion objects of classes in Scala, but with a subtype relationship that mirrors the associated classes. We can emulate that as follows: abstract class Serializable {
static const Map<Type, SerializableCompanion> _companion = {
Serializable: const SerializableCompanion(),
A: const ACompanion(),
};
static SerializableCompanion companion<X extends Serializable>() =>
_companion[X];
}
class SerializableCompanion {
const SerializableCompanion();
Serializable fromInteger(int i) => throw "AbstractInstantionError";
}
class A implements Serializable {
int foo;
A(this.foo);
}
class ACompanion implements SerializableCompanion {
const ACompanion();
A fromInteger(int i) => A(i);
}
T bar<T extends Serializable>(int nb) {
return Serializable.companion<T>().fromInteger(nb);
}
main() {
var wow = bar<A>(42);
print(wow.foo);
} We have to write The Another issue is that ...
pub fn main() {
let wow = bar::<Serializable>(10);
...
} This illustrates that the invocation in Rust is actually quite similar to the one in the above Dart emulation, because it will provide the actual trait object to We could turn this emulation into a language design proposal for Dart, although it isn't trivial. Apart from the syntactic noise (that we may or may not choose to reduce by means of some desugaring), the essential missing feature is a special kind of dependent type that would allow us to know that the class SerializableCompanion<X extends Serializable> {
const SerializableCompanion();
X fromInteger(int i) => ... // Ignore the body, the important point is the return type.
} In the emulation we also need to thread that type argument around, e.g., We wouldn't want to add these dependent types to the Dart type system as such, but it is a line of thinking that we could apply when deciding on how to understand a particular syntax for doing this, and also in the implementation of the static analysis. In particular, any data structures similar to the So there's a non-trivial amount of work to do in order to create such a feature as a language construct, but the emulation might also be useful in its own right. |
@MarvinHannott wrote:
You do get those outcomes, but it's not a sham. ;-) When When you do There's nothing inconsistent about this, and other languages will do similar things. E.g., if you have an instance public class Main {
public static void main(String[] args) {
Class<Main> c = Main.class;
System.out.println(c); // 'class Main'.
System.out.println(c.getClass()); // 'class java.lang.Class'.
}
} |
The subtype relation in the metaclasses (i.e. static members) wouldn't necessarily have to mirror the class's own subtyping, and I think there are good arguments that it should not. In particular, that follows Dart's current behavior where static members and constructors are not inherited. To get polymorphism for static members, you could have explicit extends and implements clauses and those could be completely independent of the class's own clauses: class A {
a() { print("A.a"); }
}
class MA {
ma() { print("MA.ma()"); }
}
class B extends A static extends MA {
b() { print("B.b");
}
class C implements A static implements MA {
a() { print("C.a()"); }
static ma() { print("C.ma()"); }
}
test<T extends A static extends MA>(T value) {
value.a(); // OK.
T.ma(); // OK. We know the "static interface" of T has ma().
}
test<B>(B());
test<C>(C()); I don't know if this actually hangs together, but back when Gilad was working on the metaclass stuff, I felt like there was something there. |
@munificent wrote:
Right, I remember that we agreed on that already several years ago. ;-) But if there is no connection between those two subtype relationships then we can't make any assumptions about T bar<T extends Serializable>(int nb) {
return T.fromInteger(nb);
} We do know that In this comment I tried to move a bit closer to something which would actually preserve the connection between the two subtype relationships (such that |
That's why in my example I wrote a test<T extends A static extends MA>(T value) { // <--
value.a(); // OK.
T.ma(); // OK. We know the "static interface" of T has ma().
} That's the part where you define the type that the type argument's metaclass must be a subtype of. |
OK, that makes sense! It would make |
That's exactly right. Because now you are "doing more" with T, so the constraints placed upon it are more stringent. |
It'll really useful feature for serializable classes. |
I think I am in the wrong place here, but I cannot find what I am looking for and this seems to be the closest place. I also tend to have a hard time understanding the intricacies of programming language architectures, so please forgive me if this is a simple misunderstanding on my part. What about static fields in an abstract class? If I want every one of my repos to have a static String called collectionName, Is there any way to have the abstract BaseRepo enforce that?
I also would like some way to enforce that every impl. or inheriting class is a singleton. but I have not yet found a way to force that by way of inheritance.
maybe there is a way to use mix ins, or extensions? idk. this is what I am going for, but I haven't found a way to make them happen. |
Now it's 2020,Is there any progress? |
Hi, I also came across the need of a generic type abstract class Base {
factory Base.fromMap(Map<String, dynamic> map) => null;
Map<String, dynamic> toMap() => null;
}
class A<T extends Base> {
T b;
createB(map) {
b = T.fromMap(map);
}
} I use a workaround too lookup the right type at runtime and use fromMap there. abstract class Base {
factory Base.fromMap(Map<String, dynamic> map) => null;
Map<String, dynamic> toMap() => null;
}
class Response<T extends Base> {
String id;
T result;
Response({this.id, this.result});
Map<String, dynamic> toMap() {
return {
'id': id,
'result': result.toMap(),
};
}
static Response<T> fromMap<T extends Base>(Map<String, dynamic> map) {
if (map == null) return null;
return Response(
id: map['id'],
result: mappingT2Class(T, (map['result'])),
);
}
@override
String toString() {
return 'id: $id; result: ${result.toString()}';
}
}
dynamic mappingT2Class(t, map) {
Type myType = t;
switch (myType) {
case BaseChild:
return BaseChild.fromMap(map);
}
}
class BaseChild implements Base {
final String id;
BaseChild(this.id);
@override
Map<String, dynamic> toMap() {
return {'id': id};
}
@override
factory BaseChild.fromMap(Map<String, dynamic> map) {
return BaseChild(map['id']);
}
@override
String toString() {
return 'id: $id';
}
}
It works ok, but I have to manually add the type I like to use to that function |
Hi, Will dart improve in this area? I think annotation could really help. Please also consider adding @childrenOverride, which means the direct child should override the abstract method even it is an abstract class, this can deeply benefit generated code, such as built_value.
|
Sorry for the very long delay.
Why do you want to enforce that? What does that enable you to do? Since static methods are not polymorphic, even if you require a handful of classes to all have the same static method, that doesn't actually give you any new affordances. |
Please see the following arrangement:
Is this possible to acquire this result without abstract static methods? Thank you. |
I also would like this. Let me offer a concrete use case: namespaces that support nesting and differ in storage methods (in-memory; local sqlite; remote api; …). So they all have the same methods (get/set with paths). To implement set, I would like to do something like this in an abstract superclass. Assume I can access the class of the current object using .class and use that as the default constructor: abstract class Namespace {
final String address;
Namespace(): address = Uuid();
void set({List<String> address, String it}) {
if (address.length > 1) {
_set_with_string(address: address[0], it: it
} else {
_set_with_string(address: address[0], it: this.class(address: address.sublist(1), it: it).address, nested: true)
}
}
void _set_with_string({String address, String it, Boolean: nested = false});
} This is a fine design, but afaict, not currently doable in Dart. The best workaround I can think of is to have a new_instance method in each subclass that invokes the constructor. Which isn't horrible, but certainly counts as a design smell caused by a language issue. |
We have a similar use case with Page routes in flutter. What I want is this:
So implementations need to to provide this name:
This can be checked later in onGenerateRoute, so we can create the proper page:
Removing the static method from inside the class here just causes problems, mainly it makes refactoring harder, since you now need to rename 2 code instances |
In your example, @esDotDev, I don't see what value an abstract static declaration provides. You're calling |
It's about declaring a contract, like any interface is meant to. Our team can have a convention, when you create a new link you must extend AppLink, then the compiler will inform them of methods they need to provide, and as our codebase morphs or changes, will inform them of any new methods that need to be provided. This is no different than non-static inheritance, you could make the same argument there: what's the point of an abstract method, when you will just get an error if you try and use said method when it is called directly on the concrete class. This is not much different than saying every StatelessWidget needs a The core value is that the code is self documenting, and we're leaning on the compiler to lower devs cognitive load. They don't need to remember all the methods AppLink's are supposed to implement they can just look at / extend the base class, and the compiler will offer them code-completion for all required methods. If we add a new abstract property to AppLink in the future, the compiler lights up, and shows us everywhere that needs fixing (whether those methods are currently called or not), imagine I have some pages, currently commented out, so they are not "in use", when using an interface, those classes will also get flagged for a refactor, instead of only lighting up later when those pages are used. |
Won't this be covered by static type extension? type extension<T extends InheritedWidget> on T {
static T of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<T>();
}
}
class MyInheritedWidget extends InheritedWidget {
}
MyInheritedWidget.of(context); |
C# is shipping this in v11 See https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members |
I would love to see this feature in dart, this will help in various use cases like serialization, it would be similar to what is available in C# which would allow the jsonEncode and jsonDecode functions to work for any custom object that implements the MapSerializable interface Maybe due to dart current limitations, I think this could only be applied to data classes in the future exampleabstract interface class MapSerializable<T> {
Map<String, dynamic> toMap();
static T fromMap(Map<String, dynamic> map);
}
data class Person implements MapSerializable<Person>{
String name;
Person(this.name);
@override
Map<String, dynamic> toMap() {
return {'name':name};
}
@override
static Person fromMap(Map<String, dynamic> map){
return Person(map['name']);
}
}
void main(List<String> args) {
var p = Person('John Doe');
print(jsonEncode(p));
//{"name":"John Doe"}
Person person = jsonDecode<Person>('{"name":"John Doe"}');
print(person.name);
//'John Doe'
}
|
Any progress here? |
Looks like I never spelled out how it is possible to use standard object oriented techniques to achieve something that is quite similar to virtual static methods by creating a shadow hierarchy of singletons, playing the role as "the static member holders" of the primary hierarchy. This is in particular something you can do if you "own" the entire hierarchy (that is, if you have marked all of the classes as abstract final class Serializable {
static const Map<Type, _SerializableCompanion> _companion = {
Serializable: const _SerializableCompanion<Serializable>(),
A: const _ACompanion<A>(),
B: const _BCompanion<B>(),
};
static _SerializableCompanion<X> type<X extends Serializable>() =>
_companion[X] as _SerializableCompanion<X>;
}
final class A implements Serializable {
int memberOfA;
A(this.memberOfA);
}
final class B implements Serializable {
void memberOfB() => print('Running memberOfB!');
}
class _SerializableCompanion<X extends Serializable> {
const _SerializableCompanion();
X fromInteger(int i) => throw "AbstractInstantionError";
X fromNothing() => throw "AbstractInstantionError";
void virtualStaticMethod() => print("Most general virtual static method.");
}
class _ACompanion<X extends A> extends _SerializableCompanion<X> {
const _ACompanion();
X fromInteger(int i) => A(i) as X;
X fromNothing() => A(0) as X;
void virtualStaticMethod() => print("Virtual static method for `A`.");
}
class _BCompanion<X extends B> extends _SerializableCompanion<X> {
const _BCompanion();
X fromNothing() => B() as X;
}
T deSerialize<T extends Serializable>(int nb) {
return Serializable.type<T>().fromInteger(nb);
}
main() {
// Similar to dependency injection.
var TypeA = Serializable.type<A>();
var TypeB = Serializable.type<B>();
// Companion methods resemble static methods or constructors.
var a = TypeA.fromNothing();
var b = TypeB.fromNothing();
a = TypeA.fromInteger(42); // We have more than one way to create an `A`.
try {
// We may or may not have support for some constructors with some types.
Serializable.type<B>().fromInteger(-1);
} catch (_) {
print("We can't create a `B` from an integer.\n");
}
// `a` is an `A`, and it gets the type `A` by inference.
print('a is A: ${a is A}.');
print('a.memberOfA: ${a.memberOfA}.');
// `b` is similar.
print('b is B: ${b is B}.');
b.memberOfB();
print('');
// Call a static method on the given type `X`.
void doCall<X extends Serializable>() {
// Note that we do not know the value of `X` here.
Serializable.type<X>().virtualStaticMethod();
}
doCall<A>(); // Calls "A.virtualStaticMethod".
doCall<B>(); // Calls "Serializable.virtualStaticMethod".
} If you do not own the entire hierarchy then it's going to be less straightforward to create This is probably not 'progress', but it may still be useful to be aware of. ;-) |
What about a keyword, similar to @munificent's proposal? How about adding /// Ensures subclasses have both a [fromJson] constructor and [toJson] method.
abstract class JsonSerializable {
/// Normal constructors and static members work as they do today.
static void alwaysThrows() => throw UnimplementedError();
/// This constructor must be overridden in all subclasses because `abstract` was used.
abstract static JsonSerializable.fromJson(Map json);
/// A normal abstract method
Map toJson();
}
class User extends JsonSerializable {
final String name;
@override // <-- must be overridden
User.fromJson(Map json) : name = json["name"];
@override
Map toJson() => {"name": name};
@override
bool operator ==(Object other) => other is User && other.name == name;
}
bool testJson<T extends JsonSerializable>(T obj) => T.fromJson(obj.toJson()) == obj; |
First of all, for the idea to work, the type var user = User.fromJson({"name": "foo"});
testJson<JsonSerializable>(user); // Calls `JsonSerializable.fromJson`. The Can a class opt out, fx an The way This also effectively introduces a kind of "Self type" in that it can generalize over constructors which return something of their own type. class Copyable {
abstract static Copyable.copy(Copyable self);
}
class Duplicati implements Copyable {
final int state;
Duplicati(this.state);
@override
Duplicate copy(Duplicati other) : this(other.state);
} This override has the wrong signature, it should accept any class Copyable<T extends Copyable<T>> {
abstract static Copyable.copy(T self);
}
class Duplicati implements Copyable<Duplicati> {
final int state;
Duplicati(this.state);
@override
Duplicate copy(Duplicati other) : this(other.state);
}
T clone<T extends Copyable<T>>(T original) => T.copy(original); or we may need to allow convariant overrides: Duplicate copy(covariant Duplicati other) => Duplicate(other.state); which is a weird concept, and pretty unsafe, so probably not what we want. So either F-bounded polymorphism (with its issues) or unsafe code, if we allow abstracting over functions that take themselves as argument. Or a lack of ability to have a static method which returns the same type. class C<T extends JsonSerializable> extends T {
C(Object? v) : super.fromJson(v); /// Calls the known-to-exist `T.fromJson`
} But what about mixins: mixin M on JsonSerializable {
final int bananas;
M(this.bananas, Object? o) : super.fromJson(o);
String toJsonString() => toJson().toString();
} If we know that the super-class has a specific constructor, can we then allow a mixin to have a constructor? Still, requiring every subclass to implement the same static interface is bothersome. Virtual staticsA virtual static method is a static method declared on a type, which is inherited by all subtypes. A subclass may override the member, but must do so with a signature that is a valid override of the inherited member, just like an instance member. Invoking a virtual static on a type is just like invoking a static. Invoking a virtual static on a type variable will use the static declared or inherited by the actual type bound to the type variable at runtime. A virtual constructor is a constructor which must be implemented by all subtypes. It is not inherited, since the constructor must return the self-type. It cannot be omitted, even on abstract types, but it'll have to be a factory constructor on those. (Mixins can have factory constructors!) If we require that a generative virtual constructor is overridden by another generative virtual constructor, not a factory, then we can allow mixins with It's annoying to have to implement constructors on every subclass, even those which don't need it. Static interfacesThat is, if we're going to make methods callable on type variables, I'd rather introduce static interfaces that class can choose to implement, but which is not tied to its normal interfaces. static interface Copyable<T extends Copyable<T>> {
Copyable.copy(T original);
}
class Duplicati implements static Copyable<Duplicati> {
final int state;
Duplicate(this.state);
@override // if you want to write it
Duplicati.copy(Duplicati original) : this(original.state);
}
T clone<T extends static Copyable<T>>(T value) => T.clone(value); Here the Then we can define sub-static-interfaces static interface MultiCopy<T extends MultiCopy<T>> implements Copyable<T> {
static List<T> copyAll(Iterable<T> originals);
}
class Triplicati static implements MultiCopy<Triplicati> {
final int state;
Triplicati(this.state);
Triplicati.copy(Triplicati original) : this(original.state);
static List<Triplicati> copyAll(Iterable<Triplicati> originals) => [for (var o in originals) Triplicati.copy(o)];
String toString() => "<$state>";
}
Set<T> setCopy<T static implements Copyable<T>(Iterable<T> values) => {for (var value in values) T.copy(value)};
List<List<T>> deepCopy<T static implements MultiCopy<T>>(List<List<T>> lists) =>
[for (var list in lists) T.copyAll(list)];
void main() {
var trips = [Triplicati(1), Triplicati(2)];
var tripOfTrips = [trips, trips];
print(setCopy(trips)); // {<1>, <2>}
print(deepCopy(tripOfTrips)); // [[<1>, <2>], [<1>, <2>]];
} A subclass of a class that implements a static interface, doesn't have to implement the same static interface. abstract class JsonEncodable {
Object? toJson();
}
static interface JsonDecodable {
JsonDecodable.fromJson(Object? _);
} Here Then you can write: class JsonSerializer<T extends JsonEncodable static implements JsonDecodable> {
T decode(Object? source) => T.fromJson(source);
Object? encode(T value) => value.toJson();
} (We'd probably want to give a name to that kind of combination constraint. Maybe It's a kind of intersection type on types themselves, but it's only intersection static interfaces on top of a single type. (Proposed as-is. All syntax subject to change. No guarantees of soundness. May contain nuts.) I start to get why Kotlin defined companion objects as objects with normal methods and able to implement interfaces. Not that it solves everything, you still cannot get the companion object from a generic type parameter. |
Good points, I didn't think of those. But:
So at the call site, the compiler can tell that an abstract method will be used and flag it. This is a case that doesn't need to be handled today -- it's impossible to get an instance of an abstract class -- but there is a similar example, calling
Putting the generic on the class works, but is annoying, as you said. What about putting it on the method itself? class Copyable {
abstract static T copy<T extends Copyable>(T self);
} Note this isn't a constructor anymore but it's still called and used the same way.
Agreed, the usual semantics with
Seems like what @munificent was suggesting earlier in the thread. My main problems with that are:
abstract class JsonEncodable { Map toJson(); }
static interface JsonDecodable { JsonDecodable.fromJson(); }
typedef JsonSerializable = JsonEncodable & static JsonDecodable; But then what about methods like: JsonSerializable copyWith(String key, Object? value) {
final json = toJson();
json[key] = value;
return fromJson(json);
} Where would that go? In general, I'm not really as sympathetic to "what if the subclasses don't want it?" because by using As a final example, consider Cloud Firestore's typedef FromFirestore<T> = T Function(snapshot, options);
typedef ToFirestore<T> = Map<String, dynamic> Function(T value, options);
CollectionReference<R> withConverter<R extends Object?>({
required FromFirestore<R> fromFirestore,
required ToFirestore<R> toFirestore,
});
class User {
User.fromFirestore(snapshot, options) { ... }
Map toFirestore(options) => { ... };
}
final users = firestore.collection("users").withConverter(
fromFirestore: User.fromFirestore,
toFirestore: (value, options) => value.toFirestore(options),
); This could instead be: abstract class FirestoreObject {
abstract static FirestoreObject fromFirestore(snapshot, options);
Map<String, dynamic> toFirestore(options);
}
CollectionReference<T extends FirestoreObject> withConverter();
class User extends FirestoreObject {
@override
static User fromFirestore(snapshot, options) { ... }
@override
Map toFirestore(options) => { ... };
}
final users = firestore.collection("users").withConverter<User>(); |
think that the syntax of the class that is inheriting should remain the same, that is, keeping the reserved word static in the inherited method that is, focusing on overriding and/or implementing static methods, and perhaps leaving constructors aside in abstract interfaces abstract class FirestoreObject {
abstract static FirestoreObject fromFirestore(snapshot, options);
abstract Map<String, dynamic> toFirestore(options);
}
CollectionReference<T extends FirestoreObject> withConverter();
class User implements FirestoreObject {
@override
static User.fromFirestore(snapshot, options) { ... }
@override
Map toFirestore(options) => { ... };
}
|
The point of virtual abstract methods is that they are not resolved statically. All the compiler can see is a type parameter with a bound
Since the goal here is to allow
My "static interfaces" idea above is the third item. I'd at least go for the second item. Number one is just too unsafe.
Not sure how that would be implemented. It means that every I agree that static interfaces are probably too complicated, because they don't mix with normal interfaces, so you can't require a type to implement a static interface. If we make virtual static methods be carried along with the interface, like instance methods, then:
It still means that Consider: T copyWith<T extends new JsonSerializable>(T object, String key, Object? value) =>
T.fromJson(object.toJson()..[key] = value); The Or we can just require every subclass of Or we can introduce structural constraints instead of static interfaces: T copyWith<T extends JsonSerializable{T.fromJson(Object?)}>(T object, String key, Object? value) =>
T.fromJson(object.toJson()..[key] = value); which means (But then there is surely no way to promote to satisfy such a constraint, which would be something like So yes, "all subtypes must implement static virtual members too" is probably the only thing that can fly, and abstract classes will just have to implement constructors as factories. |
Very good point 😅. Yeah, the issue of having a self type is a bit hard to avoid. I'm still not sure I understand a path forward without the standard trick of
Totally agree. I don't love the structural constraints, it's way too different and the name is important imo.
I kinda like this idea. I can see why you would want to allow an abstract class though, as many abstract classes have concrete factory constructors that pick a preferred concrete subclass to construct. |
Couldn't the dart implementation be similar to the C# Static abstract members implementation, with just a few syntax differences such as replacing the ":" with "extends" among other small differences from the dart style instead of the C# code style? c#// Interface specifies static properties and operators
interface IAddable<T> where T : IAddable<T>
{
static abstract T Zero { get; }
static abstract T operator +(T t1, T t2);
}
// Classes and structs (including built-ins) can implement interface
struct Int32 : …, IAddable<Int32>
{
static Int32 I.operator +(Int32 x, Int32 y) => x + y; // Explicit
public static int Zero => 0; // Implicit
}
// Generic algorithms can use static members on T
public static T AddAll<T>(T[] ts) where T : IAddable<T>
{
T result = T.Zero; // Call static operator
foreach (T t in ts) { result += t; } // Use `+`
return result;
}
// Generic method can be applied to built-in and user-defined types
int sixtyThree = AddAll(new [] { 1, 2, 4, 8, 16, 32 }); @Levi-Lesches @lrhn |
The C# design is basically that interfaces can have static members too, and implementing classes have to implement those static members. They solve the issue of interfaces, abstract classes in Dart, not implementing the interface, by not allowing an interface type as argument to a type parameter which has a type with static members as bound. C# already has a separate way to specify constructors on type parameters, so they don't need to incorporate the self type into the static interface to support constructors. On the other hand, Dart should be able to just assume that a constructor on type variable C# also has virtual static methods. It's not necessary for having abstract static methods, if needed it can be added later. |
Аny plans for this 5 year discussion? fromJson is present in 95% of applications, maybe it's worth raising the priority of this issue? |
No plans. We are hard at work on macros, and are hope is that those will be a better tool for solving problems related to serialization like |
That will help for sure, but it won't make it statically typed. We can't ever pass a type in for serialization unless it's registered. Instead, we need to pass a factory. It's not a great API, and static interfaces can also do way more. |
Macros are good, but I don't want to solve programming problems with metaprogramming. Also, I need a simple way to create a contract by which a class will contain a static method or at least a factory. |
Hi, trying to produce some generic code, I discovered that interfaces can't have non-implemented static methods.
But it would be a nice feature to allow this.
I can illustrate it by this piece of Rust code:
I tried to produce a non-working equivalent in Dart:
The text was updated successfully, but these errors were encountered: