For the pitched multiline expressions this is not holding true. Even as of right now it is possible to write the code where last line isnât clearly denoting returned value.
Considering the initial example of this function,
- If we treat this from standpoint that both branches return a value, then this function has two ends that here presented as âequalâ.
- If we treat this as an expression, then whole
if
denotes the end of a function.
Both cases can be (subjectively easy) deduced in the example. Neither of them, however, follow that function has either one end or it is clearly denoted by the closing brace.
It also can be rewritten with only one return given currently available expressions, and that reads nicely:
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
return if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
}
}
I would still prefer version with guard
in the first place, but thatâs question of preference.
I also agree with @austintatious that problematic cases tend to be more complex and less expressive in the first place, they quite often present much more critical parts of the program as well. Large if
-else
-if
or switch
is reality for many apps, more than I wish I end up working with code that looks like this one, with me not being an author of this code, so I have to guess what was intent of the author.
There is also another issue brought by implicit return
s. Modifying example with rays to match proposed behaviour, plus one more replacement to get this code:
func hit(_ ray: Ray, _ bounds: Range<Double>) -> Hit? {
// snip...
// Find the nearest root that lies in the acceptable range.
var root = (h - sqrtd) / a
if !bounds.surrounds(root) {
root = (h + sqrtd) / a
if !bounds.surrounds(root) {
return nil
}
}
let point = ray.at(root)
hit(with: point) // <- changed
}
It is now harder to reason just by the bare last line whatâs happening: does this function returns a value or not? I might be brought there via debugger, so I am not yet aware of the signature, and explicit return can help understand more from this narrow context.
Comparing to Rust, for instance, it is a bit better by at least signalling via missing semicolon:
fn hit(_ ray: Ray, _ bounds: Range<Double>) -> Option<Hit> {
// snip...
// Find the nearest root that lies in the acceptable range.
let mut root = (h - sqrtd) / a;
if !bounds.surrounds(root) {
root = (h + sqrtd) / a;
if !bounds.surrounds(root) {
return None;
}
}
let point = ray.at(root);
hit(point)
}
IMHO it is still harder to get compared to explicit return, but there is some hint for better understanding.
This all can be justified that it is not the reason to block the change, because someone always be able to abuse feature to the point of non-readable code. I donât find this argument relative to this change: the question for me not that it can be abused, but that it promotes certain way to write code to the point where long if
/switch
expressions with side effects are extremely easy to create and language has explicitly been adjusted for that.