dart-lang / dart-lang/language

User-defined mutating operators.

Open
#2,580 1 comment 4 reactions 0 assignees View on GitHub
feature
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.