To me this proposal adds little value with a weird syntax. Its main problem is that the new keyword then doesn't read naturally. It looks like a linguistic syntax error, especially when then is interweaved with else.
Below I propose an enhanced syntax, which is a bit more general and easier to read. I use special symbols which may conflict with the parser. Please consider mostly the introduced features, not the exact symbols.
The main purpose of the new "branch expression" (BE) syntax is to define a "branch value" without repeating the lvalue or an auxiliary constant/variable name. In this respect, a BE looks more like a functional expression, since it tries to anonymize the intermediate variables.
The following code is very similar to the example in the original proposal:
switch scalar.value {
case 0..<0x80: longLValue = 1
case 0x80..<0x0800: longLValue = 2
case 0x0800..<0x1_0000: longLValue = 3
default:
log("this is unexpected, investigate this")
longLValue = 4
}
The purpose is to avoid the repetition of longLValue, which might be a long string containing subscripts, and also to avoid introducing a new intermediate constant/variable. The abbreviated code shall look as a single assignment, with one composite expression as rvalue.
As already commented, replacing longLValue = with return is a NO-GO, but it worths mentioning a few similarities. If the above code was the body of a closure it would look like:
switch scalar.value {
case 0..<0x80: return 1
case 0x80..<0x0800: return 2
case 0x0800..<0x1_0000: return 3
default:
log("this is unexpected, investigate this")
return 4
}
The return value is anonymous. Once we hit a return, we set the return value and we skip the rest of the code. The return keyword has the meaning "set and done". (In a pure function, it also has the equivalent but more relaxed meaning: "set the return value and you may skip the rest; you can continue if you want, but nothing is going to change, since the return value is already set and it cannot be overwritten or unset, so all subsequent return are not effective".)
The above "set and done" semantics are exactly what is needed in place of longLValue = in the original code, or in a more complex BE body.
Principles of the BE syntax:
- A BE is introduced with the special symbol
.=. This makes it clear that the rvalue has special semantics for the inner .= symbols.
- The lvalue of the wanted value and the assignent operator are replaced with
.=, which can be spelled as "set and done".
- Effectively the lvalue is pulled before the BE and it is factored out.
- The BE is a single
if/switch/do/for/while expression, but its inner body may be complex (as complex as the body of a closure).
- All BE expressions (not only
if) may have an else {} part. The else body is executed in case of a miss (no .= is executed in the main body).
The above example would be abbreviated as:
longLValue .= switch scalar.value {
case 0..<0x80: .= 1
case 0x80..<0x0800: .= 2
case 0x0800..<0x1_0000: .= 3
default:
log("this is unexpected, investigate this")
.= 4
}
As it is common in Swift, .= may be omitted if it can be inferred (as in the first 3 cases above).
Visually the .= symbol acts like a continuation marker. The outer .= continues to the applicable inner .=, depending on the conditions. Writing the code is as easy as thinking of the quivalent closure and replacing return with .=. When reading the full syntax, .= is interpretted as "set and done". Reading the simplified code after reducing .=, is more challenging (and often more natural), as with other simplifications in Swift.
Another example:
let first: Int? .= for i in 0..<a.count {
if found(a[i]) { .= i }
} else {
.= nil
}
An interesting extension is that an optional lvalue can take its default nil value in case of a BE miss, therefore the else part above can be omitted.
The "set and done" semantics is a good fit to "first-of" type of algorithms, e.g., finding the first index, as in the example above. It is not such a good fit to "full-scan" algorithms which need to update a state, e.g., finding the max value.
Another interesting extension is nested BE's. I don't think that this feature worths the effort, and I don't recommend it, but it worths considering it as an exercise. The inner body of a BE can be any body, which could contain another (nested) BE. We can use the symbol ..= for the inner anonymous value, which allows full flexibility on how the two anonymous variables are set. This flexibility introduces some new challenges, e.g., which part of the code to skip if the inner value is set first. (Another approach is to restrict the syntax like in nested closures, where each return has a well-defined context.)
Regarding the inner .= symbol: of course it can be replaced with another symbol or another keyword. I would prefer to have a symbol which allows an extension to nested BE's (just as a possibility, even if it is never implemented).
Regarding the outer .= symbol: maybe it can be replaced by =, if the compiler can recognize that the rvalue is a BE. In general I don't like this approach. The rvalue is something different than what we had before. Using the same syntax for the assignment just hides this information. We pretend that nothing changed, although there are new semantics. This approach quite often fires back, since it over-simplifies syntax by adding confusion (and by the way this is a favorable approach in Swift).