Strong -1 to this proposal.
I only supported if/switch
as expressions because it allowed me to be a bit quicker when I had enums with trivial properties per case, allowing me to remove the return_
s. Also it looks a little prettier in Xcode ;)
However before that, I thought that if/switch
(didn't think about do
) should have allowed single-line implicit returns/returns at all because it's the "returned" expression in that branch of the control flow/scope. I think that this would have enhanced the idea of "implicit returns single-expression(s)" because this would have still allowed stuff like:
// 1
func foo(bar: Bool) -> String {
if bar {
"isBar"
} else {
// do other stuff, taking up multiple lines
return "isNotBar"
}
}
// 2 - assume in `enum Cookie`
var stringValue: String {
switch (self) {
case .chocolateChip:
"Chocolate Chip"
case .oatmeal:
"Oatmeal"
case .sugar(let frostingColor):
if frostingColor == .blue {
"Blue Sugar"
} else {
// do other stuff, taking up multiple lines
return "Not Blue Sugar"
}
}
}
// 3 - I think that this would have been allowed as well:
// follow the control flow to the returned/resolved value in scope
var myValue = if bar {
"isBar"
} else {
// do other stuff, taking up multiple lines
return "isNotBar"
}
I find this a bit more natural: follow the path of control flow and the first (and only) single-line expression is the expected return/resolving value or we require a return
-ed expression for multiple lines within a scope, like we already have. I also find this more consistent since in any context that would require a resolved expression (functions, computed properties, closures, etc.), the rules are the same; just follow the control flow.
if/switch
becoming expressions suited my needs desires and I was ... content with it. I still saw it as a small but maintainable anti-pattern and only as a nice-to-have: it allowed me to remove a few return_
s in my enum
s.
However, I find all of the ideas to push this proposal forward to be very anti-Swift-like and only make the language more confusing:
-
Introducing a new keyword
This only adds another keyword that people have to learn about and is exclusive to this feature. This is why I'm glad that we don't use yield
. I echo all resistance against then
in this thread and resist all new keywords.
-
First/Last/Any bare expression
But I also echo all resistance/resist against no keywords. First/Last/Any bare expressions would be - again - exclusive to this feature. It doesn't make sense why this specific feature/context would have different value returning/resolving rules from the rest of the language. So, I would say implementing this new rule would have to be universal*. But while Scala may have this rule and some find it cool, it's very anti-Swift-like and makes reasoning about return values difficult. This "allowance" would change the semantics of the entire language, and I think we should solidify exactly what the syntax for returning/resolving a value looks like in all contexts/scopes**.
-
Using return
This fits in with my thoughts of how it should work in the first place. Not because if/switch
are expressions, but because: it's the returned/resolved expression in that branch of the control flow (see example 3 above).
I would/will never use or allow multi-line/statement if/switch/do
expressions in code that I control (even my example 3 above), or even the currently existing single-line expressions if it becomes more than a basic binary if/else
. It's an anti-pattern to the safer, easier to read, and more declarative approach of the following below. It requires at most 2 more lines (declaration and traditional newline) and prevents headaches in the future with reading and indentation/linting.
let foo: Int
// if/switch/do your stuff
// foo must be resolved like always in all branches
print(foo)
Counter Proposal
I counter-propose that we change how this entire feature is implemented. Remove if/switch/do
resolving as expressions and replace with the control-flow resolving rules:
- follow control flow/scopes to returned values
- scopes expecting a returned value with multiple lines must have a
return _
***
- scopes expecting a returned value with a single-line-expression will implicitly return that value***
I find this to be more natural, more easily teachable/educational****, and concrete. I also think that this wouldn't break the usage of the existing feature.
Yeah sure this is syntactically equivalent to this feature just with return
for multi-line blocks but is at least technically different in description.
tldr: this entire feature should have just been syntactic sugar for an immediately returning closure - we would already have this and any other future issues sorted out.
*or it would at least begin the slippery slope of proposal threads where that is the new rule.
**see above for my reasoning about control flow.
***But what about nested control statements?
This is ugly, but I surely won't stop you from writing it and it doesn't break existing rules or introduce special rules. I leave all of the other cases as an exercise to the reader.
let a: Int = if b {
if c {
if d {
// do other stuff, taking up multiple lines
return 1
} else if e {
2
} else {
3
}
} else {
if f {
4
} else {
// do other stuff, taking up multiple lines
return 5
}
}
} else {
6
}
****We should never have to say: "if you want to return a value in a function, here are X rules. If you want to return a value in this feature/scope, use Y rules. If you want to return a value in this feature/scope, use Z rules. There is no reason why it shouldn't be consistent.