Open ranquild opened 7 years ago
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.
@rrousselGit @eernstg @pedromassango has there been any decisions on the next phase of dart feature updates? is there any document available so we can see the timeline for the next feature updates?
@easazade, we're working on other features at this point and we haven't decided on anything new here. As an aside, you may be interested in https://github.com/dart-lang/language/issues?q=is%3Aopen+is%3Aissue+label%3Apatterns which is somewhat related.
@eernstg
Is there any plan to make patterns serializable, without code generation? (Also with support for stuff like BuiltValueField - wirename)?
No promises, but it's on the radar..
Is it still on the radar 3 years later ?
I've been checking this topic for quite long time. I hope it is somewhere on top of the guys, let's add this
list.
FYI folks: null-safety was a 🐻 – (a bear, something very hard, to be clear) – we're wrapping that up now. Thanks for your patience.
FYI folks: null-safety was a bear – (a bear, something very hard, to be clear) – we're wrapping that up now. Thanks for your patience.
Great! Adding data classes will be a walk in the park then :wink:
==> Why the thumbs down ?
@cedvdb I imagine because many people (me included) would like this to be a compile time feature for performance and type safety.
If there was some sort of reflection without builder this wouldn't be needed at all, would it ? You could just extend a class that would reflect on itself to see generate a toString, copy, etc..
Imo if there is runtime reflection this is not needed. The issue is that we need runtime reflection though.
==> Why the thumbs down ?
https://github.com/flutter/flutter/issues/1150
Flutter does not allow reflection because it will increase package size.
this would be fantastic if it allowed, the longed-for serialization for JSON natively without the need for manual code generation or reflection in time of execution
Today practically all applications depend on serialization for JSON, a modern language like dart should already have a form of native serialization in the language, being obliged to use manual codegen or typing serialization manually is something very unpleasant
@munificent how can we get an update on the static meta programming features of dart or maybe other solutions dart team is working on to solve the problems mentioned in this thread? should we create another thread since dart classes is not gonna happen as a solution for the problems mentioned here?
and thanks for all the efforts you guys are making. dart has changed a lot since last 2 years.
@munificent how can we get an update on the static meta programming features of dart or maybe other solutions dart team is working on to solve the problems mentioned in this thread? should we create another thread since dart classes is not gonna happen as a solution for the problems mentioned here?
and thanks for all the efforts you guys are making. dart has changed a lot since last 2 years.
I think that, the static metaprogramming adds many more possibilities, the data class should also be added to the language, although these below end up using the static metaprogramming.
Every time we worry about not contaminating our domain layer with external dependencies, which would make it necessary to implement the data classes every time we start a new project. This is a necessity in the language, it has also been the most requested feature in the community for years, why not listen to it?
@jennsenr If the Dart team chooses to implement data classes in terms of static metaprogramming, they will still put it into the standard library, as far as I am concerned. So, you won't have to depend on external dependencies, neither implement by yourself anyway.
There is an issue for metaprogramming at #1482, and I proposed one possible syntax at #1507. The conversation for both of these got a bit long, but the details themselves should be in the top comment.
The thing is that meta programming seems to be a big feature that needs careful planning. While data class is more restricted in scope. I'm still wondering if the Record proposal could solve this issue, ie if a record could be used as Data class ?
The thing is that meta programming seems to be a big feature that needs careful planning.
That's true, but it also has the potential to cover data classes as well as many other cases (like JSON serialization, mentioned here). So it may be a net positive in terms of language team efficiency to do metaprogramming and not a slew of smaller one-off features like data classes.
This is a huge barrier for us looking at flutter as a cross platform option. I have data class extensively in my apps there's no way I can tolerate a language without a similiar feature.
@brucexia, data classes are already possible as just regular classes, this proposal just adds some shortcuts:
final
, which is just one keyword (and not always desired)==
, which is often overriden anyway to specify which fields are relevant (or for deep equality)hashCode
, which would be nice but hardly a show-stopper. I often use the hashCode
of a unique field, like email
toString
, but people might have different preferences. I personally wouldn't like the proposed definitioncopyWith
, but there are some problems with a copyWith
that has nullable parameterstoJson
(and maybe fromJson
?)... and JSON codec.
Right, edited to fix.
If it helps the discussion I can draw attention to #1565, my second take on code-gen/static metaprogramming that is a lot better than my first one and directly handles data classes as one of its most common use-cases. I implemented all of the above as examples in that issue.
My proposal for Data Classes:
data Product {
String id; // will be required
String name = 'Unknown';
bool favorite = false ;
String? extra ; // nullable, optional, without a default value.
}
var a = Product{ id: '1', name: 'test', favorite: true };
var b = Product{ id: '2' }; // with the default values
var c = Product{ id: '3' , extra: 'foo' }; // defining the optional value.
var keys = a.getKeys(); // list of fields names.
var name = a['name']; // A filed should be accessible dynamically, to facilitate codes that can work in any kind of data class.
var name = a.name; // or can be accessed as a normal field.
var json = a.toJson();
var d = Product.fromJson(json);
extension ProductExtension on Product {
bool get isUnknown => name == 'Unknown' ;
}
@brucexia, data classes are already possible as just regular classes, this proposal just adds some shortcuts:
- implicit
final
, which is just one keyword (and not always desired)- implicit
==
, which is often overriden anyway to specify which fields are relevant (or for deep equality)- implicit
hashCode
, which would be nice but hardly a show-stopper. I often use thehashCode
of a unique field, like- implicit
toString
, but people might have different preferences. I personally wouldn't like the proposed definition- implicit
copyWith
, but there are some problems with acopyWith
that has nullable parameters- EDIT: implicit
toJson
(and maybefromJson
?)
This is exactly what Data class is used for (comparing to Kotlin off course). I'm not sure about fromJson, toJson methods as those conversions are not language features. What if inside of that Data class you'd like to store object that is not serializable to json? Plus those conversions ofter require specific parameters naming inside json, or more complex logic. It would be nice to have better auto serializer to json. But I think it should not be mixed with Data class concept.
Also allow mutable data classes, or able to set mutable fields.
Also allow mutable data classes, or able to set mutable fields.
No. The whole point of DataClasses is that they are immutable. If one attribute is mutable, the whole class becomes mutable.
If you want a partially mutable class just use class, and set the attributes you want immutable to final.
Also allow mutable data classes, or able to set mutable fields.
You probably meant a built-in copy
/copyWith
method which returns a totally new object.
It's not hard to make mutable modifier
I feel like a built-in copy
/ copyWith
is already something we would expect with data classes. Indirect mutability would therefore be possible by just creating a new immutable instance with the changes you want. Allowing data classes to have mutable fields would just overcomplicate things without bringing any actual benefits to the table.
It seems we can all agree that "data class" just means "a regular class with some pre-written methods, like copyWith
, ==
, etc.". I don't understand all the talk about mutable fields. Maybe it doesn't fit your design pattern, but that doesn't mean it's invalid, and it certainly doesn't "complicate" things -- all fields are currently mutable by default, and you can use final
to indicate immutability. I don't think Dart should be adding in more restrictions when we're asking for more conveniences.
I liked the idea to explicitly define if you want a final (immutable) data class. Here's an example using final
to define an immutable data class:
final data Product {
String id; // will be required
String name = 'Unknown';
bool favorite = false ;
String? extra ; // nullable, optional, without a default value.
}
I liked the idea to explicitly define if you want a final (immutable) data class.
For those who want this feature today, you can use the @immutable
annotation from package:meta
. All Flutter StatelessWidget
s use it. You'll get a warning if you try adding non-final fields to an @immutable
class (or any of its subclasses).
(Granted, the annotation doesn't do anything snazzy like automatic copyWith
, ==
, etc, which is what this issue is about).
Personally, I'd like exactly the same functionality and syntax as records in C# done with compile time reflection that was fully implemented at the same time so that things like a dart version of Entity Framework was possible as a result. (this would also eliminate most needs for mirrors and in fact most of the use of build_runner too.)
... with compile time reflection ...
Then you might be interested in static metaprogramming. Compile-time reflection doesn't currently exist, but static metaprogramming needs it to, so it's being taken as a given there (which is why I pointed out the similarities earlier in this thread).
In general, static metaprogramming (and code-gen) is such a general, broad concept that it will introduce many other more specific features along the way and will enable loads more to be added. That's why it's a useful topic to follow, even if you don't plan on using it directly.
@Levi-Lesches For the record C# has compile time reflection available and EF Core that will be released with .net 6 uses it to generate all of the queries and eliminate the reflection to great effect (300-400% improvement in speed, startup times and more). .NET is in the process of doing compile time reflection on basically everything that doesn't use strings for loads etc. including JSON deserialization, protobuf amongst other things.
It would be a HUGE win for Dart to have this functionality for the same reasons.
I could be wrong, but this feature would also alleviate some of the rewriting required between layers in some clean architecture approaches, especially in cases where you have a tight relation between data and entities.
For instance, I've seen some Kotlin approaches to clean architecture where it can reduce it drastically, as shown by this answer at stackoverflow. (Not a Kotlin expert myself)
Def a huge win.
It seems we can all agree that "data class" just means "a regular class with some pre-written methods, like
copyWith
,==
, etc.". I don't understand all the talk about mutable fields. Maybe it doesn't fit your design pattern, but that doesn't mean it's invalid, and it certainly doesn't "complicate" things -- all fields are currently mutable by default, and you can usefinal
to indicate immutability.
This is exactly why I think it's good to push on static metaprogramming and let users express something like data classes at the library level. Because different users have different policies that they want data classes to have. If we just pick one and bake it into the language, we'll pick wrong for some subset of users.
If we can make static metaprogramming expressive enough that users can write their own data class packages whose resulting syntax is as nice as a built in language feature, then users who want different policies can pick a package that does what they want.
If we can make static metaprogramming expressive enough that users can write their own data class packages whose resulting syntax is as nice as a built in language feature, then users who want different policies can pick a package that does what they want.
I don't see the current proposal for static meta programing as a true syntax improvement for code-generation.
We are very limited by the inability to overwrite an element, such as adding/removing parameters to a function or converting a function into a class.
This forces packages to create new objects instead of modify existing ones. Which means users have to manipulate an object with a name different than what they defined
This is a significant pain point.
@rrousselGit, this has been discussed extensively at #1482, #1507, and #1565 (and others). Feel free to add your opinion there, but I think most (including myself) are vehemently opposed to allowing metaprogramming to overwrite user code, as that would lead to a LOT of complicated workflows that users can get lost in, both accidentally and deliberately. All the proposals I've seen build off of that, ie, generating complex code that fits nicely with hand-written code, but doesn't modify in any way.
Speaking of, @munificent, can you take a look at #1565? It's my second draft (with #1507 being the first) of a static metaprogramming proposal. It focuses mainly on the end-user experience: writing, applying, and composing macros with only a small discussion on how the compiler will handle the change, since modules will probably handle the latter. I'd appreciate any feedback.
All the proposals I've seen build off of that, ie, generating complex code that fits nicely with hand-written code, but doesn't modify in any way.
I feel that this is the opposite.
Numerous usecases would want to override the definition, including data-classes.
One issue with the current data-classes proposals is, they are defined as:
@data
class Foo {
final int property;
}
And expect the macro to generate a constructor.
The problem is, this prevents users from defining how the constructor would look like. Some properties may be required, some optional, some named, some positional, or even some with default values. We also have to consider super constructors or whether the constructor allows "const" or not.
That's why Freezed is constructor-first:
@freezed
class Foo with _$Foo {
const factory Foo({@Default(42) int property}) = _Foo;
}
But this still doesn't support super constructor and has other syntax issues (like the @Default
because param = x
is not allowed on factory constructors).
So realistically, we would want to write:
@freezed
class Foo extends Base {
const Foo({int property = 42}): super(...);
}
But this would require overriding the definition (to change the constructor parameters into this.property
or to make the constructor a factory)
Arguably we could declare the class as _$Foo
and generate Foo
, but this causes other issues:
_$Foo
instead of the generated Foo
. Which is a common source of confusion with current code-generators using this approach. These workarounds once again degrade the user experience to be sub-par what a language feature would offer.
Constructors are a massive problem for metaprogramming because they don't have any form of inheritance (so you can't get them by extends
or implements
), they're not shared with mixins (no with
eithers), and extensions can't define constructors.
That's why partial classes (#252) would help. With partial classes, you'd write:
// foo.dart
part "foo.g.dart";
@data
partial class Foo {
final int property;
}
and the macro would generate:
// foo.g.dart
part of "foo.dart";
partial class Foo {
Foo(this.property);
}
and you'd be able to use the constructor wherever you import Foo
. Of course, partial classes would also be able to split other members apart as well, not just constructors. This way, we keep hand-written and generated code separate, while allowing the compiler to recognize them as the same. (Partial classes also allow one more benefit: the generated code looks as if a human could have written it. Partial classes can be useful for humans too, and once people get used to them, it takes away some of the magic of "where did this constructor come from?")
That's why partial classes (#252) would help. With partial classes, you'd write:
There seem to be a misunderstanding. My comment was arguing against this syntax.
I am aware that the current data-class proposal skips constructors. My point is that skipping constructors is bad.
A macro should not take any assumption about whether a parameter is required/optional, positional/named, and with or without a default value. Nor can a macro know about what super constructor to use or whether const constructors are allowed or not.
Oh well if the user wants to manually specify a constructor, they can, and the macro can just ignore it.
@data
partial class Foo {
final int property;
Foo({required this.property}); // now the macro won't generate a constructor
}
Or maybe you can have a system based on annotations (like the ones in #1565):
@data
partial class Foo {
@defaultField(42)
final int number;
final bool condition;
}
// generated:
partial class Foo {
const Foo({required this.condition, this.number = 42});
}
Or whatever other conventions people want to set. And of course, if you push this to the extreme, you're better off writing your own constructor than using a million annotations. Point is, by allowing users to write their own macros with meaningful reflection, it can be worked out.
Manually writing the constructor or using annotations like @default
/@positional
is degrading the syntax
If data classes implemented using code generations have a noticeably lower quality than if they were built in the language, then I don't think we can say that we fixed the issue.
Fair point, although "degrading the syntax" is largly subjective. What do you think an ideal syntax look like? Then this issue can focus on that, separately from code-gen.
I believe users want something as close to Kotlin as possible. With Dart, that could be:
const class User(final String name, {final int? age});
Or for unions:
class UserState {
const class Loading([final Data data = null]);
class Data(final String name, {int? age}) implements Human
: assert(age == null || age > 0);
class Error(final Object error);
}
The goal being no duplication, while still having control over named vs positional parameters & co.
The current Freezed syntax being
@freezed
class UserState with _$User {
const factory UserState.loading([Default(null) Data data]) = Loading;
@Assert("age == null || age > 0")
@Implements<Human>()
factory UserState.data(String name, {int? age}) = Data;
factory UserState.error(Object error) = Error;
}
Which is passable. But still objectively worse due to default values / asserts / super constructors not being ideal, and some duplicate.
And even then, this syntax is not compatible with the current static meta programming proposals.
Is there any indication whether this is still on "the radar" circa 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:
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.