dart-lang / dart-lang/language
User-defined mutating operators.
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
Dart has "composite" operators which do an operation and an assignment: `e++`, `e += 1`, etc.
Those work brilliantly with immutable values like numbers - they take the old value, create a new value, and assign it back.
Sometimes you have a *mutable* classes that you want to "increment" or modify *in place*. Then you have to call a method.
That's OK, but not always great API.
So, consider allowing *user defined mutators*:
```dart
class IntWrapper {
int _value = 0;
void operator +=(int other) { _ value += other; }
void operator ++() { _value++; }
// ...
}
```
When the compiler sees code of the form `e1 += e2`, it checks whether the static type of `e1` declares a `+=` operator. If so, the code becomes the method invocation `e1.+=(e2)`. It can return a value if it wants to.
If the static type of `e1` does not have a `+=` operator, then it's not considered mutable by `+=`, and is instead treated as immutable. Then `e1 += e2` is treated like `e1 = e1 + e2` (where, as usual, subexpressions of `e1` is are only evaluated once).
For `++`/`--`, if `e1` implements `operator++`, then `++e1` becomes `e1..++()` and `e1++` becomes `e1.++()`, which can choose to return a "previous" value if it wants to. Mostly it'll probably just return `void`.
Then we can define something like the C# events:
```dart
class Event {
Set> _callbacks;
void operator+=(void Function(T) callback) { _callbacks.add(callback); }
void operator-=(void Function(T) callback) { _callbacks.remove(callback); }
void emit(T value) {
for (var callback in [..._callbacks]) callback(value);
}
}
```
or choose to have a mutable version of an immutable type, where you can just change the type of a variable, and the old `+=` keeps working, only now it's inlining the mutation.
```
class Vector3 {
final double x, y, z;
Vector3(this.x, this.y, this.z);
Vector3 operator+(Vector3 other) => Vector3(x + other.x, y + other.y, z + other.z);
}
class MutableVector3 implements Vector3 {
double x, y, z;
MutableVector3(this.x, this.y, this.z);
Vector3 operator+(Vector3 other) => Vector3(x + other.x, y + other.y, z + other.z);
MutableVector3 operator+=(Vector3 other) => this..x += other.x..y += other.y..z += other.z;
}
```
The argument against such a feature is that it makes code harder to read. You can't necessarily figure out what's going on when you see `e1 += e2`, whereas `e1.add(e2)` makes it clear that no assignment happens.
Also, a very good compiler can recognize `x = x + v`, inline `x + v`, allocation-sink the result, and recognize that it can just overwrite the original value of `x` because it's no longer referenced. I'm not sure our compilers are that good in genenral.
Contributor guide
Research direction
Start by reviewing the proposed mutating-operator semantics in the issue and the language specification’s existing operator and compound-assignment rules. Compare the behavior for +=, -=, ++, and --, including return values and single evaluation of subexpressions. Done means an agreed design with resolved readability and optimization concerns, plus identified specification and test entry points.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100