I was literally laying awake for hours last night thinking about this thread, not able to get to sleep. There has been a bit more back and forth since the last time @Ben_Cohen posted, but still no clearly winning ideas that would make it easy for @Ben_Cohen to draft a V2 with confidence that it would be any more acceptable to the people who are hesitant about the proposal in the first place, or to people who have strong opinions on what alternative is better.
Although I am still perfectly happy leaving things the way they are, I came up with a compromise that makes the proposal acceptable to me, and I hope others will agree.
It splits the proposal up into three parts. I think separate threads will allow for more focussed discussion, and less circular arguing, but they could all be kept together. My proposal is basically to split out the least controversial piece. Then split the rest of the proposal into two pieces. It compromises with the idea that we need a keyword for clarity without adding an obscure/unprecedented one. My idea possibly avoids adding a new keyword, while allowing the use of ones we already have in certain contexts. I think it keeps Swift more intuitive in general and more familiar to the larger programming community as well.
Part 1: Allow single line do-catch expressions.
I’ve said it before, but separating this out would make it quite easy to review and approve. Given that SE-380 is already in the language, I think even people who don’t like it that much will be fine with this in order to make the language consistent. I haven’t seen anyone in this thread express direct opposition to that part of the pitch.
If part 1 is accepted, it will inherit the functionality of parts 2 and 3:
Part 2: multi-statement if-expression in return or throw statements.
In this part we require the use of “return” or “throw” in the in the multi-statement blocks. For example:
var width: Int {
switch scalar.value {
case 0..<0x80: 1
case 0x80..<0x0800: 2
case 0x0800..<0x1_0000: 3
default:
log("this is unexpected, investigate this")
return 4
}
}
When the if-expression is a return value there is no ambiguity with using “return” since that value is being returned from the function. If you use it as a source for “throw” you would use the keyword “throw”.
More examples:
return switch scalar.value {
case 0..<0x80: 1
case 0x80..<0x0800: 2
case 0x0800..<0x1_0000: 3
default:
log("this is unexpected, investigate this")
return 4
}
throw switch CODE {
case 400: NetworkError.bad_request
case 401:
logout()
throw NetworkError.unauthorized
case 403: NetworkError.forbidden
}
This would also allow for guard condition else { return value }
within if expression if they are used as a return value or throw.
This feels very consistent to me since the multi-statement blocks follow the same rules that other function level multi-statement blocks follow.
Limiting the scope of Part 2 to return and throw statements mitigates the need to add a new keyword in a way that I hope will have more consensus.
Part 3: multi-statement if-expression as the source for an assignment
Option 1:
(I think this is the option I prefer.)
When using an if expression as an assignment, we use the bare last expression as the result.
Yes, you can write gnarly code this way, but just don’t.
Yes, that feels inconsistent with functions that require the use of return if they are more than one line, but Kotlin has the same inconsistency and we can be okay with that.
Yes, there can be ambiguity with implicit static member look up, but I think we’re just going to have to be okay with that; using a semicolon or write out the type name are appropriate ways to disambiguate that already exist in the language.
Option 2:
When using an if-expression as an assignment, introduce a new key word to mark the result. Right now it seems like the discussion is between “then” and “use”. I think either are easy enough for Swift developers to learn, but more insight may come from showing example code to non-swift developers and see if they can intuit the meaning to test for readability.