Apologies to sound like a broken record, but would anyone mind to help me understand why that in and of itself is a serious pain point? If we are going through this trouble for single and multiple line expressions is to avoid it… but why? What is the big nasty problem they caused and the better things we can do only because we have an alternative to that {…}()?
Happy to take it to direct messages, it is really asking for help here hehe :).
The question is jarring to me, because it’s implying that those who can „handle“ implicit returns are fine with them and those who cannot are not.
The same argument could be made about braces surrounding if branches. The point I’m trying to make is that it’s fine until it isn’t. The benefits don’t even come close to outweighing the downsides IMO.
A -1 for me, mostly on the basis that I found this greatly confusing every time I had to work with code written on a language that supports this, because I have to double check every function to determine what's actually being returned in each case due to the lack of an obvious return statement.
@discardableResult functions already have their values implicitly returned from single line closures. Forcing them to be implicitly discarded from non-void multi line functions where the last statement matches up with the return type would not be backwards compatible, but I can certainly understand adding a warning that the function is not in fact being discarded when used in an untyped closure, and list the three ways in which that could be rectified:
Typing the closure or the variable it's assigned to if it's immediately executed.
Explicitly discarding the result of the function.
Explicitly returning the result of the function
Personally, I think that @discardableResult was a well meaning, but ultimately incorrect, decision to generalize the ability to implicitly discard functions returning the empty tuple and it's a shame that we're stuck with it.
I certainly don't find @discardableResult functions being hard to work with a reason to reject this proposal, as they already are hard to work with.
I am willing to live with either break {label}: value or using semicolons ala Rust as a compromise, but I would hate to lose Swift's lovely lack of end-of-line punctuation.
What is the big nasty problem they caused and the better things we can do only because we have an alternative to that {…}()?
It adds two extra lines, it adds a level of indentation, it introduces a closure scope so you won’t be able to return from the outer function for example, and it’s ugly.
I would also like a serious thoughtful and well-reasoned answer to this. Both this proposal and SE-0380 seem to really be motivated by the elimination of immediately-executed closures.
But I have been unable to find any direct reference as to why that should be a goal, and what the issues with such closures are. In trying to get rid of them we're making the language vastly more complicated and ambiguous.
I'd appreciated it if the Core Team Language Steering Group would weigh in on why Immediately Executed Closures are seemingly "Bad™" and why we're going to such an effort to minimize their usage.
EDITED TO ADD:
This should've been directed at the Language Steering Group, not the Core Team.
The use of "serious" in the initial request is not implying that anything in here has been "unserious" before. I used it to specifically request answers that are thoughtful and convey an understanding of the nuance around the situation.
This also requires returns, plus some closure ceremony. But here the returns are more than ceremony – they require extra cognitive load to understand they are returning from a closure, not the outer function.
But that’s not going to solve the problem of return in trailing closures, which is at least as big of an issue IMO:
func a() {
for x in 0..<10 {
print("\(x)") // prints 0 1 2 3 4 5
if x == 5 {
return
}
}
print("this line never prints")
}
func b() {
(0..<10).forEach {
print("\(x)") // prints 0 1 2 3 4 5 6 7 8 9 10
if x == 5 {
return // does not affect behavior
}
}
print("this line always prints")
}
Counterpoint: this is the correct level of syntactic salt to discourage people from stuffing many lines of logic into a single in-place expression, while still making it possible to do so when necessary.
Four characters in “{}()” is quite short, so the amount of salt is small.
The scoping from the braces and the extra level of indentation together make it abundantly clear which statements are grouped together.
Within each branch of an if or switch expression it is already impossible to return from an outer function, so the closure scope has no effect on that.
Being “ugly” is entirely subjective. An immediately-called closure uses the same braces and parentheses for scopes and function calls as the rest of the language. It is standard Swift syntax composed in a standard way.
i remember in ancient times, the worry was that the optimizer didn’t understand these, and so people trained themselves to avoid immediately executed closure literals.
i doubt these concerns are valid today, although i wish Swift Godbolt were still online so we could actually check to be sure.
i think immediately-executed closure literals are a completely fine pattern today.
Immediately-applied closures and local functions are at most a function call; they should never allocate a closure. In optimized builds, since they are generally single-use private functions, they should normally be inlined as well.
The scoping from the braces and the extra level of indentation together make it abundantly clear which statements are grouped together.
The single expression that could be an if that spans across 20 lines. If you want to put a single print log statement before that if you’d have to embed this if inside a {}() - indenting all those lines. And then you remove that print - you’ll have to indent it back. This is a huge deal IMHO.
Within each branch of an if or switch expression it is already impossible to return from an outer function, so the closure scope has no effect on that.
I was thinking about “do” expressions. I do not see this mentioned in the pitch - do they also prohibit returns?
Being “ugly” is entirely subjective
Probably. To my eye the ugly thing about it is this: }(. I believe for the similar reason we’ve got the syntax optimisation of trailing closures to not have }) sequence.
There are places where closures make sense, and places where they do not, and for-loops are of the second type. Do not use forEach, use for-in loops instead.
…Which actually shows that you can write some bad code today. Just let people be able to drop the “return” as is proposed here and let’s find out how that will be used in a good way. Do not fear bad code, just don’t write it.
Omission isn't prohibition. But while this pitch might seem orthogonal to the the problem, it's actually just an addressing of the subset of the problem.
This pitch doesn't offer anything that IICEs don't already offer, and doesn't match their control flow options. I don't care either way about last expression as return value, but it doesn't fix do/catches that need to return multiple expressions like the then pitch would.
IICE:
let value = {
print("something")
guard condition else { return 1 }
print("something else")
return 2
} ()
The only option, with this pitch, is …OMG indentation we can't have indentation!
let value = do {
print("something")
if !condition { 1 } else {
print("something else")
2
}
}