Language: Add data classes

Created on 31 Oct 2017  ·  56Comments  ·  Source: dart-lang/language

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.

request

Most helpful comment

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

All 56 comments

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

Or inline make it shorter?

data class User(String name, int age)

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.
They might tackle it after Dart 2.
The built_value package works well enough for me.

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

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

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 toJson and fromJson methods to the generated code so data class instances can be easily (de)serializable.

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

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.

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

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

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.

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

@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.
https://medium.com/@wasyl/kotlin-developers-thoughts-on-dart-1f60c4ad21ad

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,
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 ?

@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!
Please!

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.

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.

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 🤔?

@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

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.

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.

@charafau ok you're right

Hi!
Any update on this? :)

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.

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

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.

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

I need it too =)

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 😄

Well NNBD is out. So. 😁

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

Is this experiment related?

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

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

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

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.

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

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

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

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.

One interesting thing .. is the possibility to have a cache of the serial version of the instance

I'd wish to make sure that instances of value classes do not have identity, that is, a compiler and runtime is allowed to copy them freely (e.g., in order to use inline allocation in an activation record), and it would also be allowed to reuse an existing instance (the extreme case being that the instances are canonicalized, but we can also have a mixture). It is possible to detect via identical whether two expressions evaluate to the same instance of a value class or they evaluate to different instances with the same contents. But == won't see the difference, and it's basically a bug to rely on identical because we do not promise anything specific about object identity for these instances. This adds further support to your idea about caching instances of value classes: They're supposed to allow ignoring object identity.

NNBD seems to be almost done is there any timeline on this available yet?

Not yet, and we don't even have a decision to add value classes. But more than 450 thumbs up is certainly important for the development. Right now, null safety is being rolled out, and it will take a while before it is enabled by default in a stable release.

Thx @eernstg, is there any way to try @dataClass from a dev build or branch? I tried it the other day, but it didn't seem to work (static error).

There is some experimental code using @dataClass, but it is not ready for practical applications. It was used as a use case to explore an improvement of the support for kernel transformations, and there's no guarantee that a value class feature will be a direct completion of this approach.

Thx, I realize it's experimental. Just wondering if there was some way to try it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

har79 picture har79  ·  5Comments

eernstg picture eernstg  ·  5Comments

natebosch picture natebosch  ·  4Comments

leafpetersen picture leafpetersen  ·  3Comments

listepo picture listepo  ·  3Comments