Skip to content
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

Open
ranquild opened this issue Oct 31, 2017 · 141 comments
Open

Add data classes #314

ranquild opened this issue Oct 31, 2017 · 141 comments
Labels
request

Comments

@ranquild
Copy link

@ranquild ranquild commented Oct 31, 2017

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:

data class User {
  String name;
  int age;
}

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>, and copy 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:

  1. It requires writing a lot of boilerplate code
  2. It requires running watcher/manual code generation during development.
  3. It requires saving generated files to repository because code generation time is too large for big applications.

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.

@eernstg
Copy link
Member

@eernstg eernstg commented Oct 31, 2017

No promises, but it's on the radar..

@freewind
Copy link

@freewind freewind commented Apr 6, 2018

Or inline make it shorter?

data class User(String name, int age)

@dcovar
Copy link

@dcovar dcovar commented Apr 7, 2018

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.

@zoechi
Copy link

@zoechi zoechi commented Apr 8, 2018

@dcovar don't expect anything short term. It won't be part of Dart 2.
They might tackle it after Dart 2.
The built_value package works well enough for me.

@fmatosqg
Copy link

@fmatosqg fmatosqg commented Apr 29, 2018

Community could write a package similar to Lombok who autogenerates code from a valid annotated source code file.

https://projectlombok.org/

One more thing for the wishlist on either flutter/flutter#13607 or flutter/flutter#13834, not sure which

@zoechi
Copy link

@zoechi zoechi commented Apr 30, 2018

@fmatosqg https://pub.dartlang.org/packages/built_value

@Cat-sushi
Copy link

@Cat-sushi Cat-sushi commented Oct 8, 2018

User should have implicit constructor const User({this.name, this.age});, correct?

@saolof
Copy link

@saolof saolof commented Dec 25, 2018

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.

@andrewackerman
Copy link

@andrewackerman andrewackerman commented Apr 11, 2019

I support this, but suggest also adding toJson and fromJson methods to the generated code so data class instances can be easily (de)serializable.

@ivanschuetz
Copy link

@ivanschuetz ivanschuetz commented Apr 12, 2019

@andrewackerman Data classes shouldn't have more than a generic minimum to be used as domain entities, this being equals/hashCode, copy and toString. Serialization isn't a universal requirement and even less to an industry standard (which not necessarily everyone wants to use / can become outdated) like JSON.

@eernstg
Copy link
Member

@eernstg eernstg commented Apr 12, 2019

For current activities in this direction, you may want to consider also issues like the following: #117, #125, #225, #308.

@andrewackerman
Copy link

@andrewackerman andrewackerman commented Apr 12, 2019

@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 Map<String, dynamic>, which is itself easily convertible to and from JSON.

@ivanschuetz
Copy link

@ivanschuetz ivanschuetz commented Apr 12, 2019

Maybe something generic along the lines of Swift's Codable could make sense, but this is an entirely different feature. Although equals and toString are convenience as well - I remember for example in Haskell this being solved via type classes (which to be implemented require only to write a word practically). I don't know which exact considerations Jetbrains did to shape data classes the way they did in Kotlin. It's probably something along the lines that equals and toString make sense always. Serialization, as you say, it's used only "often".

@dcov
Copy link

@dcov dcov commented Apr 12, 2019

I agree with @i-schuetz that adding a Codable protocol is probably the best option. It could even make its way into Flutter plugins (and the framework itself), where the data you pass to the 'other side' has to be encoded first.

@kevmoo
Copy link
Member

@kevmoo kevmoo commented Apr 12, 2019

@leafpetersen @munificent @lrhn – should be moved to the language repo?

@leafpetersen
Copy link
Member

@leafpetersen leafpetersen commented Apr 12, 2019

@leafpetersen @munificent @lrhn – should be moved to the language repo?

yes, thanks.

@kevmoo kevmoo transferred this issue from dart-lang/sdk Apr 12, 2019
@FullstackJack
Copy link

@FullstackJack FullstackJack commented May 8, 2019

Let's kill the argument that moving to Dart (Flutter) from Kotlin is like moving back in time several years.
https://medium.com/@wasyl/kotlin-developers-thoughts-on-dart-1f60c4ad21ad

@swavkulinski
Copy link

@swavkulinski swavkulinski commented May 15, 2019

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.

@benoit-ponsero
Copy link

@benoit-ponsero benoit-ponsero commented May 23, 2019

Hello,
I'm looking for dart, the typesystem is great, it's well thought but i think there is a lack of functionnal support.
I'm a scala developer and we have "case class" for this.
It's provide toString, equals, hashCode and a copy method with optional params.

This proposal is great and could attract more developer like me. Do you know when it could be implemented ?

@jodinathan
Copy link

@jodinathan jodinathan commented May 23, 2019

@benoit-ponsero is the method copy done by reflection?

@benoit-ponsero
Copy link

@benoit-ponsero benoit-ponsero commented May 23, 2019

It's a compiler generated method.

@jodinathan
Copy link

@jodinathan jodinathan commented May 23, 2019

Then it must be tree-shaken when built with dart2js.

@agusbena
Copy link

@agusbena agusbena commented Jun 6, 2019

Here in our company we are crossing our fingers to get this feature arrive son!
Please!

@lrhn lrhn added the request label Jun 27, 2019
@Jonas-Sander
Copy link

@Jonas-Sander Jonas-Sander commented Aug 6, 2019

I'm also hoping that this will be added, a way to have a default implementation of ==, hashCode and toString would make many things much easier and faster.

@MarcelGarus
Copy link
Contributor

@MarcelGarus MarcelGarus commented Sep 5, 2019

For a more lightweight alternative to built_value, which will be syntactically closer to possible language-level data classes, I implemented a package data_classes.
Basically, you write a mutable class (like MutableUser) and it generates the immutable pendant (User) with a constructor, converters to and from the mutable class, custom ==, hashCode and toString() implementations, and a copyWith method.

@rrousselGit
Copy link

@rrousselGit rrousselGit commented Jul 31, 2021

I disagree. Asserts are quite important for data-classes IMO. And so are default values or the ability to mix both named and positional parameters.

In particular, I think a very common use-case for data-classes is a "config" class, such as ThemeData.
And often those type of classes have asserts, default values or initializers.

But with the current proposal, it is not feasible to do something like ThemeData without also specifying the constructor. And considering config classes typically have dozens of fields, this is a lot of lines wasted

@Levi-Lesches
Copy link

@Levi-Lesches Levi-Lesches commented Aug 1, 2021

So far the solution seems to be "In those cases, defines both the properties and a constructor". But this could be a painful refactoring.

There's no refactoring if we don't have meta-programming yet. They already have a constructor, the question is can they remove it.

And I don't quite see why:

@data
class Person {
  final String name;
  final int age;
}

would be preferable to:

@data
class Person {
  const Person(
    final String name, {
    final int age = 0;
  }): assert(age >= 0);
}

Besides for what I see to be obvious readability, I wrote a comment at #1769 (comment) addressing that.

@rrousselGit
Copy link

@rrousselGit rrousselGit commented Aug 1, 2021

There's no refactoring if we don't have meta-programming yet. They already have a constructor, the question is can they remove it.

The problem isn't going from nothing to static-metaprogramming. My point is about refactoring a class that is already using the @data

Say we have:

@data
class Example {
  final String a;
  final String b;
}

And we are now tasked with adding a base class (or default values, or anything else requiring a constructor). Then the change would be:

@data
- class Example {
+ class Example extends Base {
+  const Example(
+    this.a,
+    this.b,
+  ): super.named(a: a);

  final String a;
  final String b;
}

We suddenly have to write the full constructor, which is inconvenient and verbose.

Whereas with a the constructor-first approach, the diff would be:

@data
- class Example {
+ class Example extends Base {
  const Example(
    this.a,
    this.b,
-  );
+  ): super.named(a: a);
}

With this approach, only what is necessary changed.

Besides for what I see to be obvious readability, I wrote a comment at #1769 (comment) addressing that.

I answered those points, which I disagree with.
What is the issue with readability?

@Levi-Lesches
Copy link

@Levi-Lesches Levi-Lesches commented Aug 1, 2021

I wasn't really judging by "how many lines would you add in a specific scenario". I was focusing on "omitting a constructor means let the macro take over, but explicitly setting one indicates that you want to do it manually", which is a pretty intuitive structure, similar to inheritance: you inherit members by default, but can also override them when needed.

But that's just my opinion. As @munificent said, the point of making macros general is that you should be able to create your own macros (and modify existing ones) easily. Even if overriding user code isn't allowed, you can still do something like this:

@dataFromConstructor
class Person {
  const Person._(  // is only used for code-gen purposes
    final String name, {
    final int age = 0;
  }): assert(age >= 0);
}

// generates: 
class Person {
  final String name;
  final int age;
  const Person(this.name, {this.age = 0}) : 
    assert(age >= 0);
}

Or maybe you decide that Class._ is too valuable to be reserved for macros, so you can make your macro take a constructor as
a parameter:

@dataFromConstructor(Person._codeGenSpec)
class Person {
  const Person._();  // other work done here
  const Person._codeGenSpec(  // is only used for code-gen purposes
    final String name, {
    final int age = 0;
  }) : assert(age >= 0);
}

Point is, metaprogramming should allow this to be part of your design, so we don't really need to have an "official" macro for each scenario.

@rrousselGit
Copy link

@rrousselGit rrousselGit commented Aug 2, 2021

you can still do something like this:

@dataFromConstructor
class Person {
  const Person._(  // is only used for code-gen purposes

Asking users to define a private element, so that macros can then generate a variant with a desired name is IMO a bad solution.

This hinders the ability for users to control the publicity of an element. Folks may want to make a constructor private. Using this solution, we would need a gimmick tell macros whether the generated constructor should be public or not, which is undesired because this moving away from the existing conventions.

Point is, metaprogramming should allow this to be part of your design, so we don't really need to have an "official" macro for each scenario.

Official macro or not, I believe this is still an important discussion to have.
Ultimately we are trying to solve data-classes. We need to evaluate whether static-metaprogramming truly allows us to solve the problem.

My argument is that it is only a temporary fix, as no-matter which approach we take, we would suffer from various limitations. We would lose features or have to degrade the syntax.


Ultimately I think we should split the problem into two:

  • users want to improve the syntax for defining both a constructor and properties
  • users want automatically generated copyWith/==/toString/serialization methods.

IMO while I agree that static metaprogramming is a good solution to the second point, I think the first point still should be solved by the language instead of macros.

Dart could natively support:

const class Person(final String name, {final int age = 0});

as a syntax sugar for defining a constructor + properties:

class Person {
  const Person(this.name, {this.age = 0});
  final String name;
  final int age;
}

And then we could combine it with macros for everything else:

@data // generates ==/toString/copyWith/...
const class Person(final String name, {final int age = 0});

This way we solve the problem of extracting opinionated logic out of the language, but we don't compromise on the syntax.

@jodinathan
Copy link

@jodinathan jodinathan commented Aug 2, 2021

It is not a syntax sugar to final properties, but Dart already accepts final on arguments:

class Foo {
  String buf;
  
  Foo({required final String buffer}): buf = buffer;
}

@iampato
Copy link

@iampato iampato commented Aug 19, 2021

I have been following this issue for a while now

seen a lot of proposals
would we just have data classes just similar to Kotlin

data class User{
        String name; 
        int age;
}

For optional fields use null safety and let the programmer shot him/her self in the foot in terms of positioning fields:

data class User{
        String name; 
        int? age;
}

And lets use json_annotation package for json annotation

every time I use freeze I never want to look at my code is a lot and looks "messy"

@miszmaniac
Copy link

@miszmaniac miszmaniac commented Aug 20, 2021

This is almost exactly as Kotlin implementation.
The difference is that in Kotlin those properties are in shortened constructor - so you can use all the rest of the class as normal, you can add other properties, and methods etc...

I have been following this issue for a while now

seen a lot of proposals
would we just have data classes just similar to Kotlin

data class User{
        String name; 
        int age;
}

For optional fields use null safety and let the programmer shot him/her self in the foot in terms of positioning fields:

data class User{
        String name; 
        int? age;
}

And lets use json_annotation package for json annotation

every time I use freeze I never want to look at my code is a lot and looks "messy"

@iampato
Copy link

@iampato iampato commented Aug 20, 2021

This is almost exactly as Kotlin implementation.
The difference is that in Kotlin those properties are in shortened constructor - so you can use all the rest of the class as normal, you can add other properties, and methods etc...

I have been following this issue for a while now
seen a lot of proposals
would we just have data classes just similar to Kotlin

data class User{
        String name; 
        int age;
}

For optional fields use null safety and let the programmer shot him/her self in the foot in terms of positioning fields:

data class User{
        String name; 
        int? age;
}

And lets use json_annotation package for json annotation
every time I use freeze I never want to look at my code is a lot and looks "messy"

Where is the issue, I want that too, the ability to add methods inside those data classes.
Just make it simple and stupid ... this idea of macros i am not for it

@miszmaniac
Copy link

@miszmaniac miszmaniac commented Aug 23, 2021

This is almost exactly as Kotlin implementation.
The difference is that in Kotlin those properties are in shortened constructor - so you can use all the rest of the class as normal, you can add other properties, and methods etc...

I have been following this issue for a while now
seen a lot of proposals
would we just have data classes just similar to Kotlin

data class User{
        String name; 
        int age;
}

For optional fields use null safety and let the programmer shot him/her self in the foot in terms of positioning fields:

data class User{
        String name; 
        int? age;
}

And lets use json_annotation package for json annotation
every time I use freeze I never want to look at my code is a lot and looks "messy"

Where is the issue, I want that too, the ability to add methods inside those data classes.
Just make it simple and stupid ... this idea of macros i am not for it

The issue here is that your version doesn't use any constructor, so you can't distinguish between your data field and any other field. Example:

data class User {
   String firstName; 
   String lastName; 
}

but you'd want to create computed property displayName or even make first and last name private.
In Kotlin you create:

data class User(
   val firstName: String,
   private val lastName: String
) {
...
}

So you separate those autogenerated fields from anything else. I think this is better.

@erlangparasu
Copy link

@erlangparasu erlangparasu commented Sep 8, 2021

any pub package to achieve data class? like kotlin's syntax

@miszmaniac
Copy link

@miszmaniac miszmaniac commented Sep 8, 2021

@VKBobyr
Copy link

@VKBobyr VKBobyr commented Sep 10, 2021

Possible unpopular opinion, but I'd be okay with data classes only having named arguments.
Positional arguments are more mistake prone and less obvious when looking at unfamiliar code.

@esDotDev
Copy link

@esDotDev esDotDev commented Nov 1, 2021

These sorts of data class seem to be industry standard now in most modern languages.
https://www.youtube.com/watch?v=9Byvwa9yF-I

It would really be nice if dart team would make this a first class feature rather than relying on meta-programming.

Meta-programming is great for plugging holes quickly, but this seems like a more fundamental thing that should just be part of the language and there are several implementations to look at now for reference.

@miszmaniac
Copy link

@miszmaniac miszmaniac commented Nov 1, 2021

Exaclty. And while you guys argue here about how to implement this because there are mixed feelings about this. There are references in other languages. For me kotlin is the best example here.
But this thread just turned 4. 4 years and nothing. At this point there should be a decision at least😉

These sorts of data class seem to be industry standard now in most modern languages. https://www.youtube.com/watch?v=9Byvwa9yF-I

It would really be nice if dart team would make this a first class feature rather than relying on meta-programming.

Meta-programming is great for plugging holes quickly, but this seems like a more fundamental thing that should just be part of the language and there are several implementations to look at now for reference.

@esDotDev
Copy link

@esDotDev esDotDev commented Nov 1, 2021

Like I think there's a good argument to make that the pure cludge of having a .g file for basic data class features should almost disqualify it from meta-programming as a final solution.

You're talking about the difference between:

dataclass User(String firstName, String lastName);

That's the entire thing, we get what everyone wants, a nice solid little immutable dataclass. No extra files, no extra links, no extra steps it's baked into the language as a first class type.

Compare to:

part `user.g.dart`;

@DataClass
class User {
   User(String firstName, String lastName);
}

+Another file of many lines.

Think of the extra clutter across a codebase, the hassle when declaring small data classes within a larger file, the general cruft of it all spread around, the general pain when I refactor and want to move a class to another file.

@wstrange
Copy link

@wstrange wstrange commented Nov 1, 2021

My 2 cents...

The verbosity of the meta-programming example is not that bad - I think the case is a tad over stated...

It's easy to add features to a language (C++) but impossible to remove them later. A powerful, but general purpose extension model (via meta-programming) seems like a good idea that should be explored before further language changes are made.

data classes might still be a good idea - but let's see how far meta programming can take us..

@esDotDev
Copy link

@esDotDev esDotDev commented Nov 1, 2021

It's not just the verbosity of the code you write, it's the general messiness of splitting code across 2 files, over and over again, all over your project. data classes might still be a good idea Just feels like in 2017 this was a valid question, in 2021 it's kinda been answered.

Also, coming from the standpoint of 'we need this yesterday', it seems like this might be a more linear path than meta-programming. It's a clear focused feature with not a lot of ?'s, whereas meta-programming, has many more use cases and complications, and who knows when it will ever actually land.

@cedvdb
Copy link

@cedvdb cedvdb commented Nov 1, 2021

While I'd like faster releases as much as the next person, it seems like static meta programming is high on the priority list.

https://github.com/dart-lang/language/projects/1

Static meta programing issue was opened in march 2021 and already moved to being spec'ed. As a comparison this feature #1072 which is being implemented was opened in 2013. So there is hope.

I do agree that something, not specifically data class, but even records, should be part of the the language though. For a language that is marketed at being a client sided language it seems mandatory.

@AfricanElephant
Copy link

@AfricanElephant AfricanElephant commented Feb 3, 2022

I've been lurking this thread for a couple years and I want to say that it would be really nice if Dart could simply have structs or data classes or anything really, I just want to do this, (although it could be considered kind of un-Dart-like)

struct Person {
    String name;
    int age;
    // null by default
    bool? married;
}

void main() {
    final john = John(
      name: "John",
      age: 44
    ); 
    // or either
    final Person john = {
      name: "John",
      age: 44
    };

    // the former ^ is more Dart-like, imo
    
    // prints `null`
    print(john.married);
}

Edit: thread's coming up on 5 years open now...

@jodinathan
Copy link

@jodinathan jodinathan commented Feb 3, 2022

@AfricanElephant I guess that is closer to the Records proposal

@AfricanElephant
Copy link

@AfricanElephant AfricanElephant commented Feb 3, 2022

@jodinathan That link goes to localhost and I can't find it anywhere

image

edit: cropped the image so it's smaller

@jodinathan
Copy link

@jodinathan jodinathan commented Feb 3, 2022

@AfricanElephant edited

@rbdog
Copy link

@rbdog rbdog commented Feb 12, 2022

Just my two cents
It's hard for me to copy, paste, and EDIT them every routine work 😭

  • too many syntax style in my head...
  • ";" "," ":" "." "final" "required" "this"

define class

  final int a;
  final int b;
  final int c;

then, remove "final int" and write "required this." for constructor

  required this.a,
  required this.b,
  required this.c,

then, remove "required this." and write ": 0" to call it

  a: 0,
  b: 1,
  c: 2,

So let's add new syntax

define data-class (not like class syntax, you can call this params)
all final & required

params X (
  a: int,
  b: int,
  c: int,
);

we need just copying them, and change "int" to params "0", "1", "2"

@cedvdb
Copy link

@cedvdb cedvdb commented Feb 13, 2022

All those keyword you mentioned add value to the api you create. Required will make sure that if you forget a parameter the IDE will complain, or even the compiler I'm not sure., final will make sure that the variable can't be changed etc, But I'm sure you know that already.

To be honest, my personal opinion is that those are backward, those should be the default, always more restrictive by default so to speak and we would add optional when needed, because most of the time that's what we want. But that's just not how it is and all things considered it's not so ba. In your example, would a,b and c be required or optional ?

@VKBobyr
Copy link

@VKBobyr VKBobyr commented Feb 13, 2022

I don't think there's need for the required keyword in records.

If it's nullable -> it's not required
If it's non-nullable and doesn't have a default value -> it is required
If it's non-nullable and has a default value -> it is not required

But I would still rather have the required keyword for consistency with the rest of the language.

@rbdog
Copy link

@rbdog rbdog commented Feb 13, 2022

@cedvdb @VKBobyr
Thank you so much for comments. My bad, I didn't make it clear enough.

  • I want data-class(record) to be immutable, so all params are "final". (like Remi's freezed package)
  • I can't think of that answer "required" keyword is required or not. But I think it's not bad to have "only names and types" of params in data-class, as a pure data-struct. so all params are "required".
  • I favor VKBobyr's one. so all params are "named-param".

    Possible unpopular opinion, but I'd be okay with data classes only having named arguments.

About "required" keyword, there is another proposal.
#878 (comment)

Sorry for all my strange ideas and words. I'm on your side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request
Projects
None yet
Development

No branches or pull requests