dart-lang / language

Design of the Dart language
Other
2.66k stars 205 forks source link

Add data classes #314

Open ranquild opened 6 years ago

ranquild commented 6 years ago

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 commented 6 years ago

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

freewind commented 6 years ago

Or inline make it shorter?

data class User(String name, int age)
dcovar commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

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 https://github.com/flutter/flutter/issues/13607 or https://github.com/flutter/flutter/issues/13834, not sure which

zoechi commented 6 years ago

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

Cat-sushi commented 6 years ago

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

saolof commented 5 years ago

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 commented 5 years ago

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

ivnsch commented 5 years ago

@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 commented 5 years ago

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

andrewackerman commented 5 years ago

@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.

ivnsch commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

leafpetersen commented 5 years ago

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

yes, thanks.

FullstackJack commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

benoit-ponsero commented 5 years ago

It's a compiler generated method.

jodinathan commented 5 years ago

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

agusbena commented 5 years ago

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

Jonas-Sander commented 5 years ago

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 commented 5 years ago

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.

easazade commented 4 years ago

are we gonna get data classes or WHAT? i mean really who wants to use built_value when you can just add a keyword data to the class and be done with it 🤔?

charafau commented 4 years ago

@easazade please, be nice to developers, writing compilers is not easy and must be done with cause. it's not about only data classes but also pattern matchings, ASTs and stuff.

Good news is that they're being spec'ed now https://github.com/dart-lang/language/issues/546

You can check current state of language in language funnel - https://github.com/dart-lang/language/projects/1

MarcelGarus commented 4 years ago

Generally, you cannot generate extension constructors and that's a fine decision. Maybe it would be possible to make an exception to that rule for data classes? It would be great if code generation libraries could not only generate toJson(), toProto(), … as extension methods but also add fromJson, fromProto, … constructors.

fmorgado commented 4 years ago

Generally, you cannot generate extension constructors and that's a fine decision. Maybe it would be possible to make an exception to that rule for data classes? It would be great if code generation libraries could not only generate toJson(), toProto(), … as extension methods but also add fromJson, fromProto, … constructors.

Instead of adding extensions methods and constructors, why not generate a helper class instead?

When I flag a specific class X for serialization, I find it conceptually easier to work with a JsonSchema or a ProtoSchema which will take care of all the fromX() or toX() without clobbering the base class.

easazade commented 4 years ago

@charafau ok you're right

bartekpacia commented 4 years ago

Hi! Any update on this? :)

easazade commented 4 years ago

one thing that would be really helpful to add beside copy and equals override is toJson and fromJson methods. because most of the time when we are creating a data class we are writing these methods for it. Also many libraries need a way to serialize and deserialize data classes, it would be great help if many of them would use the same toJson and fromJson methods that the language provide. or whatever other solution data classes provide for serializations. the point is that all third party libraries leverage the same solution.

sysint64 commented 4 years ago

I think there should be another way to serialization and deserialization, there are a lot of other data format like yaml, xml, protobuf and so on.

leecommamichael commented 4 years ago

I think there should be another way to serialization and deserialization, there are a lot of other data format like yaml, xml, protobuf and so on.

That's fair. I enjoy the interaction between Swift's Codable structs and Decoder classes which target different data-formats.

munificent commented 4 years ago

Any update on this? :)

No update sorry. We are very focused on shipping non-nullable types (and, now, getting through the COVID-19 pandemic). Once NNBD is out the door and going well, we'll start working on the next batch of language features. Something in this area is high on the list.

easazade commented 4 years ago

I think there should be another way to serialization and deserialization, there are a lot of other data format like yaml, xml, protobuf and so on.

i completely understand you. what i'm trying to say is that it would help a lot if data classes support serialization and deserialization. so that all other third party libraries that are being developed out there leverage a single solution instead of them all try different solutions for this problem. even adding simple abstract methods for example toJson and fromJson (or any other object notation) would help a lot because the developers will always know that these methods are provided with all data classes

bgoncharuck commented 4 years ago

I need it too =)

rrousselGit commented 4 years ago

Well NNBD is out. So. 😁

It's not. There is still a lot of work to do. New things are being speced every day, and I doubt all official plugins are migrated.

I get everyone's frustration, but NNBD is not just implementing a new language feature. It's also migrating the millions of lines of code in the packages maintained by Google, the Dart SDK, and Flutter to work with NNBD

Let's give them some leeway 😄

pedromassango commented 4 years ago

Well NNBD is out. So. 😁

Until you see it on the stable version of Dart, it's not.

mnordine commented 4 years ago

Is this experiment related?

hpoul commented 4 years ago

nice find, looks good :-) https://dart-review.googlesource.com/c/sdk/+/155607/7/tests/language/value_class/creates_equals_test.dart

kevmoo commented 4 years ago

Keep in mind, these are just (very cool) experiments by an intern at the moment. 😄

mnordine commented 4 years ago

Keep in mind, these are just (very cool) experiments by an intern at the moment. 😄

Yeah, we're still gonna get excited though. 😄

gmpassos commented 4 years ago

https://www.change.org/dart_data_classes

eernstg commented 4 years ago

Yeah, we're still gonna get excited though

Sounds good! :smile:

The internship project is about expressing kernel transformations in a more principled, reusable, safe and simple manner (kernel is an intermediate language of Dart), and the value class feature is used as a good example: It is simpler than many other kernel transformations, and still non-trivial. (Well, and it's also because it would be nice to have value classes ;-). But it doesn't constitute a commitment to add a specific value class feature to Dart, that's another (ongoing) discussion.

listepo commented 4 years ago

I really liked the approach in C# 9.0, it would be great to see something like this in Dart.

rrousselGit commented 4 years ago

On an unrelated note: If data classes supported de/serialization, would this allow the deserialization to be performed directly in the IO thread?

Such that we don't have to first create a Map, then transform the Map in an Isolate, but directly create the desired object

eernstg commented 4 years ago

If supported, (de)serialization should just be a regular synchronous computation. But it might need to be computed in some special context if it takes too long etc. So that's probably not a property of the (de)serialization itself, but more like a property of the source of the data.

gmpassos commented 4 years ago

I think that it's all about productivity, or we all will be stuck into assembly.

It's not about being different, be just a new language. It's possible to get the best of each previous language, learn with the past and evolve.

gmpassos commented 4 years ago

If supported, (de)serialization should just be a regular synchronous computation. But it might need to be computed in some special context if it takes too long etc. So that's probably not a property of the (de)serialization itself, but more like a property of the source of the data.

I think that both, async and synch, serialization/deserialization should be available, since this is a application paradigm and not a language paradigm.

One interesting thing, for immutable data classes, is the possibility to have a cache of the serial version of the instance, since will always generate the same instance or the same serial bytes.