Swift has taken +=
and similar operators from C. There's quite a few of those "... and assign" operators:
*= /= %= += -=
<<= >>= &= |= ^=
&*= &+= &-= &<<= &>>=
.&= .|= .^=
These operators, however ugly they might look initially, served us well so far, both in C (for ~50 years) and in Swift.
However they are not without issues:
- ugliness (ok, this is subjective and I admit they've grown on me given the amount of time they've been with us, but still there's something wrong in how they look and feel).
- some operators are unavailable for non-obvious reason:
&&=
||=
. Perhaps this was inherited from C that didn't have those operators. - when you add a custom operator, to make it feel like "built-in" you'd also provide the "... and assign" version of it, there is no automatic generation of those.
- these operators only work well either for commutative operations or when you are substituting the left hand side: "a = a / b". This doesn't work in many non-commutative cases (e.g. division, an addition to a string: `s = "prefix" + s, matrix multiplication, etc).
- this approach doesn't support arbitrary functions, only operators (see example below).
Could we do better?
The following pre-pitch idea uses _
as a placeholder for the value being assigned:
foo[0]["a"] = foo[0]["a"] / 2 // full form
foo[0]["a"] /= 2 // existing shortcut
foo[0]["a"] = _ / 2 // proposed shortcut
foo[0]["a"] = 2 / foo[0]["a"] // full form
// existing shortcut n/a
foo[0]["a"] = 2 / _ // proposed shortcut
foo[0]["a"] = "!" + foo[0]["a"] // full form
// existing shortcut n/a
foo[0]["a"] = "!" + _ // proposed shortcut
foo[0]["a"] = max(foo[0]["a"], time) // full form
// existing shortcut n/a
foo[0]["a"] = max(_, time) // proposed shortcut
Edit: this can work for unary operations as well:
foo[0]["a"] = !foo[0]["a"] // full form
foo[0]["a"].toggle() // existing shortcut
foo[0]["a"] = !_ // proposed shortcut
foo[0]["a"] = -foo[0]["a"] // full form
foo[0]["a"].negate() // existing shortcut
foo[0]["a"] = -_ // proposed shortcut
Alternatively we could use '$' as a placeholder.
In principle having this mechanism could allow us to gradually deprecate the proliferation of "... and assign" operators at some future point and simplify Swift.
Edit: A few further notes.
Let's assume you have a type S and want to add + operation to it:
func + (lhs: Self, rhs: Self) -> Self { // base version
lhs.adding(rhs)
}
This could serve all cases below:
a = b + c
a = _ + c
a = b + _
a = _ + _
And it would be enough, however you may have a more optimal "mutating func add(Self) -> Void" operation that you want to be used in the "_" cases above. In this case you may want to add one or more variants which you want to optimize:
func + (lhs: inout Self, rhs: Self) { // optionally provided
lhs.add(rhs)
}
func + (lhs: Self, rhs: inout Self) { // optionally provided
rhs(lhs)
}
and if compiler sees them defined it picks the most appropriate operator:
a = b + c // `func + (lhs: Self, rhs: Self) -> Self`
a = _ + c // `func + (lhs: inout Self, rhs: Self)`
a = b + _ // `func + (lhs: Self, rhs: inout Self)`
a = _ + _ // `func + (lhs: inout Self, rhs: Self)` or `+ (lhs: Self, rhs: inout Self)`
If you didn't provide, say, the func + (lhs: Self, rhs: inout Self)
operation compiler would pick the base version instead:
a = b + _ // `func + (lhs: Self, rhs: Self) -> Self`
Interestingly the same approach could work for arbitrary functions:
func max(_ a: Value, _ b: Value) -> Value // base version
func max(_ a: inout Value, _ b: Value) // optionally provided
func max(_ a: Value, _ b: inout Value) // optionally provided
a = max(b, c) // base version
a = max(_, b) // func max(_ a: inout Value, _ b: Value), if unavailable - then base version
max(&a, b) // same just spelled out differently
a = max(b, _) // func max(_ a: Value, _ b: inout Value), if unavailable - then base version
max(b, &a) // same just spelled out differently
Thoughts?