-
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
Should there be a way to update a tuple? #1292
Comments
Why not follow established convention and add a method
The method would be a bit magical (not expressible in normal Dart) because normal methods can't exactly check which parameters were passed. For positional components you can then use the same names as destructuring interfaces use. |
Nice! We could just emit a hint about ignoring the value of such expressions. Of course, this is the same thing as We could also take a bit of inspiration from the mixin construct: A syntax like It is possible that regular parentheses are more natural for a record/tuple, and we would probably not want to support function invocation for a record type, so we could use this: void main() {
(int, int, {Color color}) x = (0, 1, color: Color.red);
var x2 = x(7); // Just changes x[0].
var x3 = x(_, 3); // Just changes x[1].
var x4 = x(color: Color.Blue); // Just changes x.color.
} We could allow the modifier part ( void main() {
(int, int, {Color color}) x = (0, 1, color: Color.red);
var x2 = x(7 ...); // Just changes x[0].
var x3 = x(_, 3 ...); // Just changes x[1].
var x4 = x(... color: Color.Blue); // Just changes x.color.
} This would be inconvenient if the number of positional fields is large, but that may be inconvenient in several other ways as well, so maybe that doesn't matter much. Otherwise we could of course have a way to address a specific field as well, e.g., by allowing an int to serve as a "name": void main() {
(int, int, int, int, int, int, int, int) x = (0, 1, 2, 3, 4, 5, 6, 7);
var x2 = x(6: 100); // Yields (0, 1, 2, 3, 4, 5, 100, 7).
} |
The last thing I want to do is jam more syntax into cascades. They are already semantic mystery meat. Saying now there is a setter syntax in cascades that cannot be used outside of a cascade is even stranger.
Directly calling a tuple to do record update feels a little too subtle and implicit to me. And it doesn't generalize to user-defined classes since those may already support void main() {
(int, int, {Color color}) x = (0, 1, color: Color.red);
var x2 = x with (7); // Just changes x[0].
var x3 = x with (_, 3); // Just changes x[1].
var x4 = x with (color: Color.Blue); // Just changes x.color.
} We could generalize this to say that a |
I like that. There is always a strong push toward concise syntax (favoring Syntactically, we could make void main() {
(int, int, {Color color}) x = (0, 1, color: Color.red);
var x2 = x.with(7); // Just changes x[0].
var x3 = x.with(_, 3); // Just changes x[1].
var x4 = x.with(1: 3); // Also just changes x[1].
var x5 = x.with(color: Color.Blue); // Just changes x.color.
} The extension to give |
Ah, I like |
I just discovered C# 9.0 adds a similar |
This is, in my opinion, a must have. Everyone who ever programmed extensively in ML ended up writing their own tuple update library, and it was the feature that was always brought up as a major missing piece of the language. |
Having to do |
I am wondering if long purely positional ("nameless") tuples are antipattern to begin with - I don't think neither Btw, if we make it a method call - then IDE would complete it without any additional work on IDE side which seems like a win-win situation. User does not need to lookup how to update the tuple. They just type |
I suspect this is less of an issue in Dart where the language already gives you another way to relatively easily define incrementally-updatable aggregate types: classes.
I agree that if you find yourself frequently updating giant tuples you are already hurting yourself. At some point you should accept that your code is working with a real entity and give the thing a name and a real class declaration.
It would need some minor magic to deal with the omitted parameters and non-nullability. Consider: {x int, y int} pair = (1, 2);
var tearOff = pair.with;
tearOff(y: 2); The type of But I don't think this magic would leak out elsewhere, so it doesn't seem like an overall bad idea to me. |
I don't think I believe this. I think all of the requests for adding data class |
Fair point. That definitely implies we need an update feature that works for user-defined classes too and not just records. |
I don't think it makes sense to have an update feature on class types in general. Tuples are structural and defined by their content. If you replace one of their component values and retain the rest, the result is a new tuple. You can always create a new tuple from the values of an existing tuple. Class types, aka. interfaces, are not defined in terms of their components. They are created using constructors and deconstructed (if at all) by their getters. There does not have to be any one-to-one correspondence between getter properties and constructor arguments. There can be zero, one or and arbitrary amount of differently typed public constructors. There can be any number of derived or correlated getters. There may or may not be setters. A general |
I would say, it is very easy to update mutable classes, but if you want immutable structures, you will get frustrated very soon with Dart. Record seems like exactly what Dart needs if you are interested in applying immutable patterns in Dart.
That is maybe what you normally do, but many developers are moving to immutable data structures and consider mutable classes as the exception :) |
It looks like javascript is using the spread syntax for their version of tuples/records, it seems rather clean and would work well with spreading into argument lists as mentioned here #2128 Additionally for deep updates they have a proposal that is basically a mirror of @lhrn's proposal for extractor selector chains here #2433. The syntax being a mirror image seems like it would make it a very clean feature that works as you would expect in both update contexts as well as extraction contexts. |
The idea seems to be that a record spread in a record literal will spread all the fields that are not otherwise defined for the same record literal. That allows You can then update a record by doing fiveTuple = (,,42, ...fiveTuple); It's possible, but probably a little speculative. I like the idea. We should have spreads anyway, and this is a useful way to handle conflicts. |
I still like the idea of A more interesting question though: Could we also apply spreads to record types? typedef Vec2<T> = (x: T, y: T);
typedef Vec3<T> = (...Vec2<T>, z: T); // Alternatively, `Vec2<T> with (z: T) |
Ack. And doh. Obviously a spread should concatenate positional fields, so the spread idea only works for named fields. Doing |
@lrhn, @munificent, move to |
will we have a way to update records in Dart 3? |
There are no plans to add support for any of these "functional update" members in Dart 3.0. They remain equally interesting, of course, and it may well happen later on. For now: void main() {
(int, int, {Color: color}) cpoint = ...;
// Change `cpoint` to a new record that differs only by being red.
cpoint = (cpoint.$1, cpoint.$2, color: Color.red);
} |
Thanks for Dart 3 functionality. It's awesome. void main() {
({String? name, int? id}) category = (name: 'A', id: 1);
category = (...category, name: 'B');
} |
+1 for spread operator support. To update positional fields static extension methods on generic record types work quite well in the interim: dart-more/lib/src/tuple/tuple_3.dart |
The current tuple proposal makes tuples immutable. I think that's great.
However, there will be situations where, say, you have
(int, int, {Color: color}) cpoint;
and you want to change just the color.You'd then have to do
cpoint = (cpoint[0], cpoint[1], color: Color.red);
(using my own notation for projections 😁 ).What if you had a way to say "like this tuple, with color replaced by ..."?
Idea: Allow
[]=
orname=
to be used on expressions of tuple type, but only in cascades. The result of such a cascade is a new tuple which has the same structure and values as the original, except where those values were "overwritten".Example:
cpoint = cpoint..color = Color.red;
aka.cpoint = cpoint..[#color] = Color.red;
.Tuples are still immutable, so you can't change a tuple, but you can create a new one, and then assign it to the original variable (or to something else).
The problem with this syntax is that
cpoint..color = ...
looks like it does modifycpoint
, and users will forget to do the assignmentcpoint = cpoint..[...]=...
. We can obviosuly tell them if the result of such a modification isn't used, but it's still sligtly off compared to actually mutable values.Any other ideas?
The text was updated successfully, but these errors were encountered: