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.
Is this variation of do feasible? Apologies if it's been mentioned before, it's hard to search for individual ideas.
By itself...
let sandwich: Sandwich = do {
let leaves = chooseLeaves()
let meat = chooseMeat()
let cheese = chooseCheese()
Sandwich([leaves, meat, cheese], on: .wholemeal)
}
In if/else expressions...
let goForWalk = if isSunny do {
let temperature = getTemperature()
temperature > 15 && temperature < 40
} else if isOvercast do {
let cloudiness = getCloudiness()
cloudiness < 0.8
} else if isRaining {
false
} else do {
let coinFlip = flipACoin()
coinFlip.isHeads
}
And switches...
let dinner = switch day {
case .monday, .thursday: Curry()
case .tuesday, .wednesday: Chilli()
case .friday: Falafel()
case .saturday: do {
let leaves = chooseLeaves()
let meat = chooseMeat()
let cheese = chooseCheese()
Sandwich([leaves, meat, cheese], on: .wholemeal)
}
case .sunday: Salad()
}
Of these 3 snippets you give: the first ("by itself") is the feature as proposed.
The third ("switches") would also work as proposed – but is unnecessary in this proposal because you could just write
case .saturday:
let leaves = chooseLeaves()
let meat = chooseMeat()
let cheese = chooseCheese()
// last expression in case is value of this case
Sandwich([leaves, meat, cheese], on: .wholemeal)
As I mentioned here, there's another formulation of this proposal where do expressions are the only thing that gets the "last value" treatment. They could then be used with single-expression-body switch expressions "as is" like in your snippet.
The if one is the one that isn't quite so simple:
let goForWalk = if isSunny do {
let temperature = getTemperature()
temperature > 15 && temperature < 40
}
With the "only do can be multi-statement", this would need to be
let goForWalk = if isSunny {
do {
let temperature = getTemperature()
temperature > 15 && temperature < 40
}
} else { ... }
but I expect this could fairly easily be sugared to elide the outer braces and have if predicate do { be a thing.
Sorry, I only included that so it was a complete summary.
I just liked the idea of do as a single keyword that would allow consistent wrapping of expressions that had an implicit return. It's not necessary in the switch, but it makes things consistent. In the if/else, it gets ride of the need for the extra/awkward indentation and braces in your unsugared example.
You cannot use return in its original meaning, since unlike break there's no such thing as a labelled return
You can't use a labelled break with a label outside of the closure. At the top level of the closure, break is a syntax error.
If the closure is inside an async function, and part of the expression requires await, {...}() becomes await (() async -> Type)({...}())
It makes reasoning about isolation, escapability, and copyability of local variables a lot more difficult. You may need to add extra decorations to indicate what is captured and how, when the original intention was just for a block of code to run.
Overall, Swift has a lot of syntactic sugar to hide the difference between a closure and a block of code. However, the difference between the two is far more than just syntax, and trying to sweep it under the rug is an invitation for footguns.
On the subject of do, I actually think it would be good if this worked:
let value: Int = if condition do {
print("branch 1")
1
}
else do {
print("branch 2")
2
}
And this didn't:
let value: Int = if condition {
do {
print("branch 1")
1
} // Error: Cannot convert Void to Int
}
else {
do {
print("branch 2")
2
}
}
By allowing only the "syntax error" to have new behavior, you make source compatibility a non-issue.
Making do blocks into expressions, which this pitch does, literally allows it to do everything the then pitch could do. It just removes the need to write then.
What exactly do think is different here? Both versions require the do branch and each of the catch branches to return the same type, even if the top quote doesn't explicitly mention it.