Yes. Otherwise at times (using a German idiom) you can't see the forest for the trees.
This happens anyway. The big question is, does this go too far with this pitch. I do not think so, but others think differently.
Yes. Otherwise at times (using a German idiom) you can't see the forest for the trees.
This happens anyway. The big question is, does this go too far with this pitch. I do not think so, but others think differently.
The idiom may be of German origin for all I know, but it has been in use in English for at least half a millennia. It is documented in 1546 by John Heywood, in his collection of English proverbs.
Exactly, that is the point. return is used virtually in every function, on average maybe a little more than 1 per function. For me this is too far.
You argued that the type of the closure is ambiguous here (which I don't fully agree with[1]):
@discardableResult func g() -> Int {
doSomethingElse()
return 1
}
let closure = {
g()
}
And your suggestion is that the compiler should emit a warning to enforce writing return g() explicitly instead of just g() (to make it clear that closure's type is () -> Int and not () -> Void). But then this:
let x = if condition {
g()
} else {
// ...
}
Must be considered ambiguous too (it's also doing type inference based on the result of a @discardableResult function). Yet it can't possibly be made explicit by writing return g() instead of g(), as return can't be used inside expressions. You'd need something like assign g() to make it explicit, a new keyword.
Sure, in the above example it's a bit silly to want let x to be of type Void, because it's not generally useful to create a stored variable of that type. But since the last-expression-as-return-value rule allows nesting, this:
let closure = {
if condition {
log.info("Invoking g()")
g()
} else {
// ...
}
}
Still shows the same ambiguity problem as your original example, yet return g() wouldn't be valid syntax here (if you write return g() instead of g(), then the if is no longer an expression, and you must then write return in all the other branches, the very thing this pitch is trying to avoid).
So, if anything, your argument is one for having an explicit keyword to assign variables inside multi-line expressions (at least as an option). Otherwise you're giving up the option of being more explicit when the result of a @discardableResult informs type inference in expressions.
I agree that it can be confusing at times (depending on the function name), but to me @discardableResult only means that the result can be discarded, not that it should be discarded every time the compiler has the option to do so (like in type inference in closures). So while it's nice to be able to be more explicit, I don't see it as a requirement. ↩︎
Completely agree, more options is not necessarily good for the strict systems like programming languages.
I want to note that to be fair, the change introduces new features to the language - enables multi-statement expressions. The question if is that important to alter language behaviour at a large scale? It can be addressed in less disturbing ways. Or maybe isn’t that critical to address in the end.
With this new options added to the language more to simply support a bit different style of writing I have a C++ vibe in the bad connotation - when language is just becomes more complex because of added paradigms and ways to write the code. I have never liked this side of it - you completely puzzled what’s the difference and what to use (especially if you just starting out) instead of just writing the code.
For the matter, I really appreciate Go’s approach with mandatory formatter - while this is just another extremum to handle such questions, but the benefit is that you are worry free about how write, language has decided it and you follow, no questions asked.
Adding such approach to the Swift makes no sense, as this is controversial approach after all, but limiting options to choose from to the certain level sounds reasonable. Use of keywords, especially return, in mostly imperative language, to me seems to be way below that level.
I recently edited the mentioned issue to make some things clearer and leave this reference as an answer as it is somehow a separate topic and only insofar of value here as it is could avoid source breaks without warnings in seldom cases that this pitch would bring.
I understand how syntax becomes simpler by removing keywords/symbols. I don't see how it becomes simpler by optionally keeping or omitting them.
Just to see the forest as you said, Swift has simpler syntax than Objective-C (at least for many), because it removed ceremony, not because it made ceremony optional.
This optionality is already a given throughout the Swift language and of course a question of style, and many times a good one. E.g. I often write the type of a literal array despite type inference can infer the right type, just to avoid that I or someone else would add something of a wrong type later without getting an error exactly at this place. This is a question of style and I do not see why a linter or some other obligatory check should make a decision here. The creators of a programming language might not foresee how idioms might be used in a good way, many times it might be good to have options and then it might not be good to draw a too narrow line where the optionality of constructs should stop. Of course some might not want this line where this pitch is pushing it, and this a completely reasonable opinion.
There should be two polls - one for whether or not you personally like the feature, but also to ask what linting rules you would apply to your repos, if you (perhaps hypothetically) used a linter.
The reason I ask is that, if this change made it through, I can quite easily imagine myself using implicit returns from functions, despite being -1 on them, either out of laziness or my strange desire for terseness that doing embedded C programming gave me ~20 years ago that I've not been able to shake off. But when it comes to the standard for a code base, particularly one in a commercial or professional environment, the rules would 100% be 'no implicit returns from functions'.
Careful with polls.
If it was for “traditional” commercial environments, Swift would not have type inference. This was the reason why you always had to write all those types also in later versions of Java, because corporate environments wanted them (and distrusted their own developers).
I'm not talking about traditional environments, at least not in the old-school Enterprise Java sense (I also don't really advocate for a poll!) I just wanted to illustrate how 'raising the stakes' for me increased my dislike of the proposal, and wondered if it was a productive thought experiment for other people who are on the fence.
A feature that I'd fall into using in personal projects, but disallow in professional work, feels like a feature that belongs more in loosey-goosey languages like Typescript, not a language like Swift.
OK sorry I was a bit lazy to cite you instead of the source of the idea.
The problem is it is opt out. Not opt in. The return is implicit by default and therefore your code means something different than it meant before.
If this proposal goes through you WILL be using implicit returns whether you like it or not. Your code base will be filled with implicit returns. In most cases it won’t matter much at all because you’ll just be returning Void like you already were.
But in other cases, your code will change meaning. Your if and switch statements will suddenly become expressions. Many of them will be invalid because not all branches are the same type. So you will have manually fix that. Some rare cases will be valid but produce a different type.
You can opt IN to explicitly returning but that’s not helpful because that’s not what your code was doing before the proposal.
So now you just need to refactor.
This problem is pretty small. But multiplied across every single function and closure it is definitely not small.
I don't have this feeling. The previous Pitch was the exact opposite, it had no implicit return and it had the keyword then in multi-line expressions which was not optional.
Remember that if and switch are only allowed to be expressions when assigned to a value or used as the value of a return statement. Any if or switch that is not an expression, could only become an expression under this proposal if the ifor switch is both the last statement in a function and contains no return statements. This is very unlikely to be true in a function that doesn't return void.
The only reasonable expectation of breakage lies in the use of @discardableResult functions, which already cause problems in single expression closures. Those cases often already result in an unused variable warning, as demonstrated on godbolt.
As a suggestion, I don't think it's unreasonable that the compiler should infer that a final if or switch is the return expression instead of a statement only if the inferred type of said expression is compatible with the function result.
im about as polar an opposite as could exist to a “corporate” creature and i still use type annotations everywhere. they speed up builds and make it a lot easier to read code i (or someone else) wrote a long time ago.
There's an interesting thought: What if parentheses are mandated for the "last expression" rule? Only for multi-line of course.
// 🚫 Invalid - no return
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
}
}
// ✅ Valid - explicit return on the if expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
return if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
}
}
// ✅ Valid (but you obviously wouldn't do this) - parentheses around entire if expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
(if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
})
}
// ✅ Valid - parentheses around last expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
let v = if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
}
(v)
}
// ✅ Valid - parentheses required around last line in multiline if expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
return if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
print("cell not found")
(false)
}
}
I'm not advocating for this necessarily, just throwing it out there.
To clarify, are you saying that if the "inferred type of said expression" is incompatible with the function result then it should be interpreted as a statement instead of an expression? Does that mean that the function would now return Void? What if the function signature doesn't return Void either?
Please don't second-guess people's motivations this way. Ben is pitching this and strongly arguing for it because he thinks it's a good idea, and you are arguing against it because you don't think it's a good idea. We do not need to imagine additional hidden interests.
You are right that this is not a democratic process. There is no way to make it a democratic process, even if we wanted to. The membership of these forums is far from representative, and that only gets worse as you drill down to the ever-narrower sets of people who post at all, participate in evolution, and actively engage in arguments in pitch threads. Even if we could magically solve all that, it would still be an internet poll, prone to brigading and all sorts of other manipulation. That is why we always talk about the evolution process as an opportunity to evaluate arguments and gather feedback rather than as anything like a vote.