Open edward-a opened 5 years ago
I don't quite see how that should be possible. Static methods don't get instantiated with that type, so they don't have any information about generic type parameters. When you need type information you need to use instance fields or methods.
I don't quite see how that should be possible. Static methods don't get instantiated with that type, so they don't have any information about generic type parameters. When you need type information you need to use instance fields or methods.
C# has this working, probably Dart team could look into Roslyn for insights?
C# has this working, probably Dart team could look into Roslyn for insights?
Well, at least from what I understand, even Microsoft doesn't like it: https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1000-do-not-declare-static-members-on-generic-types?view=vs-2019 Maybe I am ignorant here, but why not jsut give the static List another generic type parameter? It is inferred to be the one from the instance anyway. Does this pattern have any substantial benefits?
Well, at least from what I understand, even Microsoft doesn't like it: https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1000-do-not-declare-static-members-on-generic-types?view=vs-2019
The code analysis warning CA1000 is aimed at people who do not realize that they get one static field per combination of type parameters. This "potential misunderstanding of generics" may merit a warning in a code analysis tool but not a full-stop compiler error.
The example I presented is only to demonstrate the error. But there should be plenty of real-life use cases for static generic fields, here is one off the top of my head:
class B<T> {
int _a;
// Do not create SomeLogic for each instance for better performance and less memory
static var logicForA = SomeLogic<B<T>, int>((b) => b._a, (b, v) => b._a = v); // Error: Static members can't reference type parameters of the class.
}
class SomeLogic<T, V> {
V Function(T) _getter;
void Function(T, V) _setter;
SomeLogic(this._getter, this._setter);
void process(T obj) {
var v = _getter(obj);
// Process 'v' ...
_setter(obj, v);
}
}
class C {
var b = B<int>();
C() {
B<int>.logicForA.process(b); // Error: The class 'B' doesn't have a constructor named 'logicForA'.
}
}
There are no plans to allow per-generic-instantiation static members.
It is possible to have a model where the different classes List<int>
and List<String>
also have different static members. However, it is not without problems.
First of all, users will likely be surprised because that's not how Dart works now. Any static variable in a generic class would exist in one version per type argument combination. Using the wrong version would be a common pitfall. We may want to have static static
("I really mean it") variables then, which only exist in one variant.
It means that there migth be an unbounded number of instances of each static variable:
class C<T> {
static List<T> theList = [];
foo<T>(int n, T value) {
if (n == 0) {
theList.add(value);
} else {
C<List<T>>.foo(n - 1, <T>[value]);
}
}
If we don't make static variables on generic classes be per type-instantiation, then, for symmetry, we would also not make getters and setters be per instantiation, so they wouldn't have access to the type variables. Then all you get from C<T>.foo()
is a type parameter that you could just put on the method.
What do you think about the following use-case?
abstract class jsonSettings<T> {
static T fromJson(String json) {
return JsonMapper.deserialize<T>(json);
}
}
class SettingsA extends jsonSettings {
// 50 fields
}
class SettingsB extends jsonSettings {
// Another 50 fields
}
I hope it's clear. I want to instantiate a class from JSON using a third-party library which instantiates ready-to-use class instances of the specified type. Obviously, when I write SettingsA.fromJson()
and SettingsB.fromJson()
there's enough type information for the compiler to do everything correctly, there's no ambiguity or obvious pitfalls. I can't see any issues here from the common-sense perspective. Am I wrong and can't see something important that prevents this use-case to be allowed in dart?
First of all, you can't write SettingsA.fromJson
since static methods are not inherited.
Doing it through a type variable wouldn't change that, and there is no plan to start inheriting statics.
It also does not use static fields, which is what this issue is about.
Allowing a static method access to the type variable is less problematic, but also less useful, because it's just a type parameter.
class SettingsA extends jsonSettings<SettingsA> {
static SettingA fromJson(String json) => jsonSetting<T>.fromJson(json);
// vs
static SettingA fromJson(String json) => jsonSetting.fromJson<T>(json);
}
There is not much advantage there.
I don't quite see how that should be possible. Static methods don't get instantiated with that type, so they don't have any information about generic type parameters. When you need type information you need to use instance fields or methods.
Except that global and static
variables are initialized lazily, so it seems plausible that A<int>.list
would create a List<int>
on first access, and A<String>.list
could create a separate List<String>
.
I am trying to create an abstract class with all the common functions so that code repetition is less. For e.g I have two model classes TestModel
and TestModel1
with the same functionality of adding, getting data to Firestore but with different attributes mainly name
and name1
.
Now i wanna do TestModel.get()
to get all the data stored or TestModel.add(data)
to add data. But the problem is Static members can't reference type parameters of the class.
Now is there any workaround to the problem or some better way to achieve what I'm trynna do. Please refer to the below code for the details.
abstract class Database<T> {
/// constants
static final testModelName = 'TestModelName';
static final testModelName1 = 'TestModelName1';
/// constructor
Database(String collectionName) {
ref =
FirebaseFirestore.instance.collection(collectionName).withConverter<T>(
fromFirestore: (snapshot, _) => fromJson(snapshot.data()!),
toFirestore: (t, _) => toJson(),
);
}
static late final CollectionReference<T>
ref; // Static members can't reference type parameters of the class.
/// json serialization
Map<String, dynamic> toJson();
T fromJson(Map<String, dynamic> json);
/// helper function
static Future<List<T>> get() async {
// Static members can't reference type parameters of the class.
return (await ref.get()).docs.map((e) => e.data()).toList();
}
static Future<void> add(T data) async {
// Static members can't reference type parameters of the class.
await ref.add(data);
}
}
class TestModel extends Database<TestModel> {
TestModel(this.id, this.name) : super(Database.testModelName);
final String id;
final String name;
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
};
@override
TestModel fromJson(Map<String, dynamic> json) =>
TestModel(json['id'], json['name']);
}
class TestModel1 extends Database<TestModel1> {
TestModel1(this.id, this.name, this.name1) : super(Database.testModelName1);
final String id;
final String name;
final String name1;
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'name1': name1,
};
@override
TestModel1 fromJson(Map<String, dynamic> json) =>
TestModel1(json['id'], json['name'], json['name1']);
}
@SuTechs can you show how you want to call the get()
and add()
methods? At a glance I think everything should be instance methods and fields; I don't think static
gets you anything here...
@SuTechs can you show how you want to call the
get()
andadd()
methods? At a glance I think everything should be instance methods and fields; I don't thinkstatic
gets you anything here...
@srawlins I want static get
and static add
methods in the TestModel
and TestModel1
classes. And there can be many TestModel
classes but all will have the same implementation of get()
and add()
. So I wanted to write it once in the abstract class only, but I guess it will not work. I wanted to call get and add like TestModel.get() or TestModel1.get()
It's possible that static
in Dart doesn't mean what you think it means. Specifically, you don't need to mark a method as static
in order for the method to be shared by subclasses. In fact, there isn't any way for a method to not be shared with subclasses unless the method is private (has a name that starts with _
) and the subclasses are in a different library. Try removing static
from ref
, get
, and add
and see whether that behaves the way you want it to.
It's possible that
static
in Dart doesn't mean what you think it means. Specifically, you don't need to mark a method asstatic
in order for the method to be shared by subclasses. In fact, there isn't any way for a method to not be shared with subclasses unless the method is private (has a name that starts with_
) and the subclasses are in a different library. Try removingstatic
fromref
,get
, andadd
and see whether that behaves the way you want it to.
@srawlins If I'll remove static
then I'll have to create an object first in order to call get or add but I want the functionality of calling get
and add
like TestModel.get()
or if I'll make every class singleton then TestModel().get
but in order to make all TestModel
classes singleton, there will be a repetition of code for every TestModel
classes.
OK I'm understanding more of what you're going for. I think your best bet is to make each class singleton with little helpers to point the meaty methods in Database
to a specific static CollectionReference
.
class Database<T> {
Future<List<T>> getFrom(CollectionReference<T> ref) async {
return (await ref.get()).docs.map((e) => e.data()).toList();
}
Future<void> addTo(T data, CollectionReference<T> ref) async {
await ref.add(data);
}
}
class TestModel extends Database<TestModel> {
static late final CollectionReference<T> _ref;
static late final TestModel instance = TestModel._();
TestModel._() {
_ref =
FirebaseFirestore.instance.collection(collectionName).withConverter<T>(...);
}
// These two methods will have to be repeated in every subclass of Database,
// since static fields are not inherited.
Future<List<T>> get() => getFrom(_ref);
Future<void> add(T data) => addTo(data, _ref);
}
If I'll remove
static
then I'll have to create an object first in order to call get or add but I want the functionality of callingget
andadd
likeTestModel.get()
...
I think you already have that situation. In the code you posted, the static field ref
is only initialized in the constructor, which means that it won't be initialized until you first create an instance. If you call get
or add
before creating an instance you'll get a runtime exception.
By making it a singleton, as per Sam's rewrite, you get rid of that problem because you always invoke a method on the sole instance, which means that you can't get the order wrong.
The second problem with the original code is that if you create an instance of both TestModel
and TestModel1
, then the second constructor invocation will throw an exception because it will attempt to re-initialize ref
, which isn't allowed because it's final
.
Sam's rewrite also solves that problem by having a separate ref
in each subclass. But even in the rewrite there's no reason for the ref
to be static. And if you make it an instance field you could move it to the superclass (where T
is defined).
The third issue with the original code is, unless I'm misunderstanding the code, that the subclasses of Database
are conflating a table in a database and a row in the table. That is, the closure for fromFirestore
calls fromJson
, which creates a new instance of the same class, which ends up re-assigning the static field ref
(and would hence throw a runtime exception).
I can't tell whether the rewrite solves that problem because the relevant code has been elided.
@bwilkerson Ya I guess, what I'm tryna do is kinda wrong using my implementation. Basically, I wanted to reduce the repetition of code because all subclasses of the Database
will have exact same functionality of adding and getting the data. The only difference will be the reference and the fields. So the goal is to write all functionality such as add
, get
, delete
, etc in a superclass and all subclasses should inherit it. But the problem is that ref
is dependent on the type of Class such as TestModel
or TestModel1
, and even if I make its initialization using generic type, it won't work.
I think the best option will be trying a different approach or the use of mixins. I'm sure there will be a better approach to solve what I'm trynna do.
Came here because of a problem writing something that works just fine on C#.
I have some functional types I use with dartz package to represent a state: State
, Empty extends State
, Waiting extends State
and Success extends State
.
Since they are empty, there is no reason to not use a singleton (otherwise, the user can forget to use const
), so:
@immutable
class Success {
factory Success() => _instance;
const Success._();
static const Success _instance = Success._();
}
@immutable
class Waiting extends Success {
factory Waiting() => _instance;
const Waiting._() : super._();
static const Waiting _instance = Waiting._();
}
@immutable
class Empty extends Success {
factory Empty() => _instance;
const Empty._() : super._();
static const Empty _instance = Empty._();
}
Works fine.
Now, I have the same states, but representing a possible value in the future: StateOf<T>
, WaitingStateOf<T> extends StateOf<T>
, EmptyStateOf<T> extends StateOf<T>
and ValueStateOf<T> extends StateOf<T>
.
Now, I cannot get singleton factories because I can't have static generic methods:
@immutable
class StateOf<T> extends Success {
factory StateOf() => _instance;
const StateOf._() : super._();
static final StateOf<T> _instance = StateOf._();
}
Error: Static members can't reference type parameters of the class.
The usage would be:
final result = SomeFunctionThatReturnsEither<Failure, StateOf<int>>();
result.fold(
(failure) => I know here has some class inheriting Failure,
(success) => I know here has some class inheriting StateOf<int>,
);
On success, I could map it:
match(success)
.when<EmptyStateOf<int>>((s) => do something for empty result)
.when<WaitingStateOf<int>>((s) => show some progress indicator)
.when<ValueStateOf<int>>((s) => s.value // this is my int)
.otherwise((s) => explode)
Dart is just limiting what people can create, based on a dictatorship.
Not cool.
@JCKodel Just because something works on C# does not mean that the same thing should work in Dart. C# and Dart implement generics in a different way. C# does generic expansion - Dart does not. C#'s approach has it's own issues e.g. during AOT compilations you might get into infinite recursion trying to instantiate types.
It would help if you write a bit more exhaustive code example to explain what you try to achieve. And how exactly having a StateOf<T>._instance
helps you. This class likely should be abstract to begin with - because as I understand you want subclasses to carry actual meaning / payload.
You can write the code like this:
@immutable
class StateOf<T> extends Success {
factory StateOf() => _instance;
const StateOf._() : super._();
static const StateOf<Never> _instance = StateOf<Never>._();
}
Dart is just limiting what people can create, based on a dictatorship. Not cool.
I suggest you to take a more constructive tone in the subsequent conversation. Thanks.
Comming back to this, I'm now more willing to allow static methods access to the type arguments of a class, effectively allowing the Foo<String, int>.staticMethod()
syntax and to make the type arguments visible inside the static method.
I'd prefer if we could disallow the type parameter's use in getter return types and setter parameter types, so that we don't break the getter/field symmetry in APIs, but that migh also prevent some inventive uses of type-parameterized getters or setters. Giving people the power, and the responsibility for using it responsibly, may just be better for everybody. (There is still a symmetry: You can change any field to or from same- and fixed-typed setter/getters. But if you don't have the same types as getter-return/setter-parameter types, or if the types of static setter/getters depend on the surrounding class's type parameters, then the getter/setter is no longer representing a simple property, they're just zero/one-argument functions with special syntax.)
So if we allow it, what would the changes be:
qualifiedId<typeArgs>.id
and qualifiedId<typeArgs>.id(args)
already for constructor tear-offs and invocations.qualifiedId<typeArgs>.id
as an assignable expression today. But we clearly recognize it ("The static member 'bar' can't be accessed on a class instantiation."), so it's probably an easily removed restriction.class Comparable<T extends Comparable<T>> { abstract int compareTo(T other); static int compare<T extends Comparable<T>>(T one, T other) => one.compareTo(other); }
would not work. The Comparable.compare
tear-off would complain that it couldn't instantiate Compare
with a valid instantiation. (And a super-bounded type is not sufficient when T
is actually in scope and can be used.)So, a more likely approach would be to explicitly mark methods that use the class type parameters, say using class
instead of static
, so you get:
abstract class Foo<T extends Foo<T>> {
class Foo<T> nestType(T value) => value;
static int nextId() => _id++;
static int _id = 0;
}
where you can do:
class Bar extends Foo<Bar> {}
// ...
Foo<Bar>.nestType(Bar()); // return argument as Foo<Bar>.
Foo.nextId(); // No complaints about not type arguments, can't see them.
It's a non-virtual method on the type, not just treating the surrounding declaration as a namespace, but not an instance methods since it has no instance to work on. Non-virtual non-instance method. Class method.
But we can declare it on any other type-parameterized namespace, even ones that have no type:
extension Ext<T> on T{
class bool isIt(Object? o) => o is T;
}
... Ext<int>.isIt(1) ... // true
so it's not really on types, just on type-paramterized namespaces.
(Is that weird? And is the class
name weird on an extension
?)
Are there plans to allow referencing type parameters in static fields? It looks to be quite a limitation.