-
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
Add data classes #314
Comments
No promises, but it's on the radar.. |
Or inline make it shorter?
|
Are there any updates on this enhancement? I'm currently working with Flutter, and having come from the Kotlin/Android world, this is something that would make the transition a lot nicer. Especially when creating ViewModels, or even simple data models, this would make it a lot easier. |
@dcovar don't expect anything short term. It won't be part of Dart 2. |
Community could write a package similar to Lombok who autogenerates code from a valid annotated source code file. One more thing for the wishlist on either flutter/flutter#13607 or flutter/flutter#13834, not sure which |
|
One thing worth mentioning is that data classes and sealed classes can both be viewed as an instance of a metaclass. If enough different kinds of special-cased classes are proposed, at some point it might become better to add metaclass programming to the language, with a few individual cases of syntax sugar. Dart already kind of flirts with the idea when you look at what was needed to make the mirrors API work. |
I support this, but suggest also adding |
@andrewackerman Data classes shouldn't have more than a generic minimum to be used as domain entities, this being |
@i-schuetz Then maybe there can be some optional attributes that can be added to the class declaration so these features can be added for people who need them? Serialization may not be a universal requirement but I'd bet that it would be needed often enough that people would want to at least have the option. Otherwise it would largely defeat the purpose of having a concise data class declaration syntax but then have to manually create the (de)serialization methods. And it's not like it would need to serialize to actual JSON strings. It could serialize to |
Maybe something generic along the lines of Swift's |
I agree with @i-schuetz that adding a |
@leafpetersen @munificent @lrhn – should be moved to the language repo? |
yes, thanks. |
Let's kill the argument that moving to Dart (Flutter) from Kotlin is like moving back in time several years. |
The point of having data classes (apart from immutability) is to have implicit methods e.g. toString(), hash(), == for free. More importantly for immutable class there is a need for mutation method (e.g. Kotlin apply() aka copyWith() in other languages) with default implementation to avoid boilerplate of mutation method. |
Hello, This proposal is great and could attract more developer like me. Do you know when it could be implemented ? |
@benoit-ponsero is the method copy done by reflection? |
It's a compiler generated method. |
Then it must be tree-shaken when built with dart2js. |
Here in our company we are crossing our fingers to get this feature arrive son! |
I'm also hoping that this will be added, a way to have a default implementation of |
For a more lightweight alternative to |
I find myself needing this every day. Our application has become too large for code generators. When we run I support the proposed design in the oc. Additionally, I think it would be tremendously valuable if data classes supported reflection. This way developers can write extensions for Looking to help out with implementation any way I can. |
To anyone else experiencing these struggles I want to share some tips I've found along the way of solving the problem stated in @josiahsrc's comment. I think it relates to this issue since - in the end - data classes will be "made with macros". Indeed, if macros will ever be available, I'd still expect analysis solve this, but AFAIK macros just "move" the generated code away from Therefore, it might be worth to apply the following, depending on your case. Split your code into packagesDepending on your architecture, chances are at a macro level that your code can be split into "external details" vs "internal details" (i.e. the infamous "data layer" from architectural literature). Minimize dependencies on the
|
Regarding build_runner specifically, it causes a lot of churn for analysis because it continuously outputs files to disk. And for each of those changes the analyzer immediately analyzes it, and re-analyzes everything that imports it. So it can end up re-analyzing the entire world many times during a build_runner build in pathological cases. This problem shouldn't exist with macros, @scheglov can confirm that. There was an idea a while ago to make build_runner wait and write out all files at the very end, instead of outputting them to disk as it goes, I think @simolus3 had done some work to this end. It might be worth trying to pick that back up, as doing all these edits "at once" (or as close to that as possible) should help the analyzer. Especially if we could avoid doing the delete->write loop at all, in the case that the contents don't change. |
@jakemac53 @lucavenir I've seen those steps help analysis quite a bit. Also, writing all the files at once sounds far more performant. But, I guess, to what end? Projects will only grow, and it feels silly to need to restructure code just to make the analyzer happy. Build runner optimizations seem like a bandaid fix to the root problem here (the need for data classes). It would be excellent to be in a world where code generation isn't required for data types. |
@josiahsrc it's been argued, up here, several times, that a single imho, if analysis / build times problems are solved with macros, it's gg, there's nothing left to fear. |
@lucavenir Thanks for sharing that. After reading through it, I'm in full support of macros. Feels like a superset of data classes. I see the challenges listed, do you know the status on this feature? Eager to help out here if I can |
We are actively working on the implementations now (it was on hold for a while for patterns/records and while we were finishing up the null safety migration of Google internal code). I can't speak to any specific timelines, but there is a lot of work still left to do. |
While you're working on this please consider the copyWith issue carefully. The only solution that actually handles all cases right now is to use nullable functions. Every other solution breaks in some form or another with most tools ignoring that problem and not setting nullable values properly. Ideally this would use macros (source generators by another name), but would also add a new language construct like C# with the "with" syntax. If you could do this: final newContact = contact with { You'd have an elegant solution to the copyWith problem that I see as the core language issue with implementing this correctly and fully (without resorting to nullable functions) |
I am definitely aware of the copyWith issue - I am not specifically tackling this as a part of macros because it is really a separate language feature (which macros would want to take advantage of). There are some existing proposals out there for solving that issue I think though. |
@josiahsrc What I learned in the 7 years working with Dart in many large projects is that the best solution is to use manual serialization implementing toMap and fromMap where needed, until you have macros or data classes in Dart with serialization, I currently don't use codegen for anything so as not to disturb my workflow, if there is one thing that would help my workflow a lot, it is if there was a way to have abstract static methods in Dart |
@insinfo: instead of tedious manual implementation, you can write a simple markdown file in an easy-to-parse format, and generate the complete class definition within milliseconds. As a bonus, you have a readable documentation. Here's an example in the simplest format (you can add more columns to it). If you need more methods, you can add them in a separate section. You can generate the entire class definition (toJSON, fromJSON, whatever, tests, invocation of tests etc). It won't be difficult to come up with a general markdown format that covers all functionality currently implemented by other generators. data class Person
MethodsString toString() =>"Person: $firstName $lastName, age $age"; |
I prefer the same approach. I write all my model classes with Copilot and sometimes the file size reaches 3000+ lines of code just boilerplate of the model classes. I too find code gen disturbing my workflow. Did you try macros for generating models? |
I've been experimenting with macros recently in case anyone wants to check it out ↓ demo.mp4Repository: https://github.com/felangel/data_class *Edit: renamed the annotation to |
Nitpick: |
This is a more general issue and we are working on that too :) |
Working on improving that now as well felangel/data_class#12 |
Can you link the issue here? I love the suggested fix! |
The only way to do this right is with every copywith property generated being a function. If the function is null, don't change. If the function isn't null, you do a change and the result could be null or not if the field is nullable. |
Or use records. class A {
final int foo;
final String? bar;
A({
required this.foo,
required this.bar,
});
A copyWith({
int? foo,
(String?,)? bar,
}) {
return A(
foo: foo ?? this.foo,
bar: bar != null ? bar.$1 : this.bar,
);
}
}
main() {
final a = A(foo: 0, bar: 'a');
print(a.bar);
final b1 = a.copyWith(foo: 1);
print(b1.bar);
final b2 = a.copyWith(foo: 1, bar: ('b2',));
print(b2.bar);
final b3 = a.copyWith(foo: 1, bar: (null,));
print(b3.bar);
} This prints a
a
b2
null |
That's way more gross than the function |
#137 is probably the best one for the overall problem, with the linked #140 being imo one of the more general purpose solutions (allow non-const default values). Then copyWith becomes: Thingy copyWith({int? myField = this.myField, ...}) => Thingy(myField: myField, ...); You probably also want to layer on optionally passed parameters + ability to check if a parameter was passed, so that wrapping functions with default values is trivial. |
Thingy copyWith({int? myField = this.myField, ...}) => Thingy(myField: myField, ...); This would work for class Thingy {
int myField; // not nullable!
//...
Thingy copyWith({int myField = this.myField, ...}) => Thingy(myField: myField, ...); // this.myField is not nullable
} But the solution is not very general either: it won't work for forwarding, which is unlikely to be the only remaining cockroach. |
I agree for sure that you want the ability to conditionally pass parameters as well as check if a parameter was passed at all, for a more full fledged solution that allows wrapping of copyWith methods to work well, etc. |
I think using a generic wrapper with a very short name ( maybe void main() {
final p = Person(name: 'Peter');
final d = p.copyWith(age: With(34));
print(d); // Person(name: Peter, age: 34)
print(d.copyWith(age: With(null))); // Person(name: Peter, age: null)
}
class With<T> {
final T value;
const With(this.value);
}
class Person {
const Person({this.name, this.age});
final String? name;
final int? age;
Person copyWith({
With<String?>? name,
With<int?>? age,
}) {
return Person(
name: name == null ? this.name : name.value,
age: age == null ? this.age : age.value,
);
}
String toString() {
return 'Person(name: $name, age: $age)';
}
} |
Note Initial macro support has landed in Freezed 🥳 It currently supports copyWith/hashCode/toString & co. @Freezed()
class Person {
Person(
String name, {
required int age,
});
}
void main() {
var person = Person('John', age: 30);
person = person.copyWith(age: 31);
print(person.name); // John
print(person.age); // 31
} Some things are missing because macros aren't stable yet. But it's a start~ |
FYI just added support for the simpler copyWith-demo.mp4 |
Immutable data are used heavily for web applications today, commonly with Elm-like (redux, ngrx, ...) architectures. Most common thing web developer is doing with data is creating a copy of it with some fields changed, usually propagated to the root of state tree. JavaScript has spread operator for this. There should be a easy way to use immutable data structures in Dart. I would like to have data classes (inspired by Kotlin) in Dart. Possible API:
Compiler assumes that all fields of data class are immutable. Compiler adds
equals
implementation based on shallow equals,hashCode
implementation based on mix of all object fields,toString
implementation of the form<Type> <fieldN>=<valueN>
, andcopy
function to recreate object with some fields changed.You may argue that there is already Built value package that allows to do similar things, but we have many issues with package, mainly:
I have found that using built value actually decreases my productivity and I do my work faster with even manually writing builders for classes.
If data classes would be implemented on language level, it would increase developer productivity and optimizations can be made when compiling code to particular platform.
The text was updated successfully, but these errors were encountered: