-
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
What generated methods should structs provide? #2372
Comments
I wouldn't start adding (A more generally useful functionality would be a |
Why not have Other languages with a similar feature use this approach, for example: Data classes in Kotlin: data class Coordinate(val x: Int, val y: Int)
fun main() {
println(Coordinate(5, 10)) // Coordinate(x=5, y=10)
} Case classes in Scala: case class Coordinate(x: Int, y: Int)
println(Coordinate(5, 10)) // Coordinate(5,10) Structs in Swift: struct Coordinate {
let x: Int
let y: Int
}
print(Coordinate(x: 5, y: 10)) // Coordinate(x: 5, y: 10) Records in Java: record Coordinate(int x, int y) {}
public class Example {
public static void main(String args[]) {
System.out.println(new Coordinate(5, 10)); // Coordinate[x=5, y=10]
}
} Records in C#: using System;
record Coordinate(int X, int Y);
public class Program
{
public static void Main()
{
Console.WriteLine(new Coordinate(5, 10)); // Coordinate { X = 5, Y = 10 }
}
} |
About the generated copyWith, will it support |
@rrousselGit As described, it will support setting a property to
|
Nice! I saw that line but wasn't sure about the implications I suppose two remaining useful "functions" would be a fromJson/toJson. |
I definitely do not expect |
I think we should consider splitting this into its own feature, I suggest to look at this as an interface auto derivation feature which works for structs and classes alike, e.g. // dart.core
/// If you implement this interface compiler would derive implementations of
/// its methods in the class.
abstract class Equatable {
int get hashCode;
bool operator == (Object other);
}
/// If you implement this interface compiler would derive implementations of
/// its methods in the class.
abstract class Encodable {
Map<String, dynamic> toJson();
}
/// If you implement this interface compiler would derive implementations of
/// its methods in the class.
abstract class Encodable {
void encodeTo(Encoder e);
}
abstract class Encoder {
void addProperty(String key, Object? value);
void addEncodable(String key, Object? value);
}
class X implements Equatable, Encodable {
final int x;
final int y;
final Z z; // class Z implements Encodable
// derived by the compiler
bool operator == (Object other) {
return other is X && this.x == other.x && this.y == other.y && this.z == other.z;
}
// derived by the compiler
void encodeTo(Encoder e) {
e.addProperty('x', x);
e.addProperty('y', y);
e.addEncodable('z', z);
}
} This is a bit similar to macros but bakes in the support in the language. I think one of the open questions here is deep vs shallow equality for things like lists and other collections and how to control derivation of that. |
Wouldn't methods that iterate over fields of the class be better handled by macros? Instead of inheriting a magically generated method, you'd apply the macro yourself to show that the code is being generated. Then you could also choose between the default (ie, most popular or built-in) implementation or something else (a macro on pub.dev or your own). The logic for generating these methods would be more transparent, as anyone could inspect the macro's source code or the generated augmentation files for themselves. |
Because overriding |
I like this feature, but I'm not sure it gets you where you want in terms of brevity. Compare: data class Foo(int x, int y); vs class Foo(int x, int y) implements Equatable, Hashable, FunctionalUpdatable, DebugStringable, Encodable; Starts to get a bit wordy. |
For One interesting question here is what to do when you can't auto-implement an interface, e.g. data class Foo(int x, int y);
data class Bar(int x, Baz baz);
Foo foo;
foo.encodeTo(e); // ok
Bar bar;
bar.encodeTo(e); // *helpful* compile time error:
// bar does not implement Encodable because type Baz (of field baz) does not implement Encodable.
// Instead of default compile time error
// bar has no method encodeTo. We could also make an effort to provide similarly helpful noSuchMethod errors for the dynamic invocations and type casts (at least on the VM). My main reason for suggesting this feature is because I think there is a natural expectation that it should be generalisable to normal classes.
Well, it depends. There are pros and cons. Autoderivation of interface conformance is a batteries included approach. It does not cover all use-cases and does not address all corner cases - but it handles common path well. No need to shop for a macro on pub, etc. It's part of the language, it's easy to understand, it works the same way in any code-base. It has less overhead than macros and it does not depend on macros shipping. |
An issue with using interfaces for automatically implemented class contracts that forces restrictions on the class, is that interfaces are inherited, and so are implementations. If I do
I'd prefer a more explicit opt-in/out for each class. Maybe: class Foo(int x, int y; ==, toString) { ... } or
|
We have a very hard time getting agreement on what the canonical For structs I'd be happy with: /// Use some efficient hash for the runtime type.
int get hashCode => Object.hash($runtimetypeHash(this), this.foo, this.bar, ...);
bool _fieldEquality(ThisType other) => this.foo == other.foo && this.bar == other.bar && ...;
bool operator==(Object other) {
// Efficient, non-overridable `(this.runtimeType == other.runtimeType);`
return $sameRuntimeType(this, other) && _fieldEquality(safeCast<ThisType>(other));
} That is, equality that would agree with canonicalization (if field values do). (I'd be fine with making the For classes, a default implementation might be the same. If you need subtypes to inherit equality (always tricky), you can write it yourself. (Maybe add this to static bool sameRuntimeType(Object? a, Object? b);
static int runtimeTypeHashCode(Object? a);
static int typeHashCode<T>(); ) |
Perhaps we could do what Slava suggests without needing special language support by:
This way there isn't a second unrelated mechanism for synthesizing methods, but users also don't have to verbosely declare that all of their struct types implement these protocols. |
The struct proposal (#2360) specifies that a number of methods are automatically generated for structs. The core set, which I think are relatively uncontroversial, are equality, hashCode, copyWith method, and a default constructor are generated. In addition, I proposed a debugToString method to support debug printing in a way that is not tied to object interpolation and hence is less likely to accidentally cause type information to be retained unnecessarily (this is a common source of code size bloat in large applications).
The debugToString specification is only loosely thought through, @rakudrama had suggestions around using a more structured format.
It may also be desirable to include other generated methods, e.g. some way of providing general coding and decoding members (cc @jakemac53 )
This issue is for discussing the general question of what generated members to provide, and with what types and semantics.
It may prove useful to file sub-issues to discuss specific proposals, which can be linked in here.
The text was updated successfully, but these errors were encountered: