SE-0255: Implicit Returns from Single-Expression Functions

(Brent Royal-Gordon) #42

We already apply these exceptions to closures (though the mechanism might be slightly different). That's why these closures typecheck:

func f(_: () -> Void) {}
f { "f was called" }
func g(_: () -> Int) {}
g { fatalError() }
2 Likes
(Jon Hull) #43

I'm torn on this.

I do constantly write single line getters (and often single line operators too) without the return, and then I have to go back and add it in, so I guess my brain thinks Swift should work this way.

On the other hand, I don't think it makes sense for init (the only use I can think of is init?(){nil}). I am also not sure that we should treat something with get/set blocks as "single line".

1 Like
(Goffredo Marocchi) #44

Fair, my brain likes being explicit and thinks that inferring (well “implying” actually :)) is the mother of potential misunderstanding at some point in day to day conversations and in tech projects too... I feel like putting return everywhere where the code... returns :).

2 Likes
(Matthew Johnson) #46

We should be very clear: nothing in this proposal will stop you from doing that, or even using a linter that will force all code in a project to do so. The proposal only relaxes the requirement that all Swift code must be written this way.

1 Like
(Jordan Rose) #47

Eh. I'm in favor of the proposal, but if all code is not written a certain way, your brain doesn't get to rely on it. (That's why I've been consistently against compilation-wide warning flags.)

1 Like
(Michael) #48

I am one of them.

I can relate. Back in the day I would even ensure all my property declarations are type annotated. This was until I started using Playgrounds on a daily basis to explore different ways to approach a problem (I mostly write and support internal tools / frameworks) - it’s here that I realized how much I liked the small things the compiler does for me (type inference, implicit return from closures, etc). Changed my mind.

I would also like to add my voice to what Matt said above.

(Matt Rips) #49

I share that sentiment. At the end of the day, this sort of choice is about stylistic preference and the exercise of good judgment.

I wonder how linters and style guides currently handle, and going forward would handle, the decision to use or not use an explicit return in a single-expression closure/function/accessor. Ugh. I want to bang my head on my desk and reconsider whether the proposal is a good idea.

(Ben Cohen) #50

(posting in personal capacity, not as review manager)

I think this is a mischaracterization. The proposal itself does not make this its primary argument, instead opening with the case that return takes up disproportionate space in simple function declarations. And the strongest support on this review thread and elsewhere seems to come from the improvement it brings to both reading and writing computed properties.

Consistency is certainly a secondary argument though. The proposal suggests eliding the return on functions will "help prepare new Swift users to understand" closures, which I think is a reasonable point. Elsewhere I've suggested that the consistency with closures makes this proposal an easier bit of sugar to accept, because that consistency means the sugar arguably lowers the complexity of the language, unlike other sugar that relies on adding new features to the language.

There's some pushback here to applying this change to functions even if we accept it for getters and subscripts. My personal opinion is it would be OK to not do it for functions, but the consistency of applying the same rule across the board means its worth doing and outweighs the reasons to not.

I find the choice between closures and functions to be a lot blurrier than others seem to. For example, suppose you find yourself writing

numbers.map { $0*2 }

Maybe for readability or because you are using this closure in a couple of different places, so you give it a name:

let double = { $0*2 }
numbers.map(double)

But then it turns out you're using it on not-integers, so you have to give it some type context:

let double = { i -> Double in i*2 }

At this point you're getting close to "may as well make it a function" territory:

func double(_ i: Double) -> Double { return i*2 }

To push it over the edge, you might want to make it generic, which would have to be a function:

func double<T: Numeric>(_ i: T) -> T { return i*2 }

At what seems like an arbitrary point in this otherwise smooth transition up the ladder of expressivity, you are forced to introduce a return. Why? Seems arbitrary to me. Not a big deal, maybe, but this is somewhere the consistency would make the language better IMO, by smoothing transition between these two forms. So while I'm strongly in favor of this proposal for the purpose of getters, I'm also in favor of it applying across the board.

26 Likes
(Michael) #51

When it comes to SwiftLint today, - implicit_return rule is disabled by default.

#52

That's true for any additive change, but:

That's my problem with the proposal. I see in my (anecdotal) experience that most single-line closure omits return, but I can see non-trivial amount of those who will adopt the proposal, as well as those who will refuse to do so, so it'll put some strain on the reader anyway. The consistency and ease of parsing arguments are quite weak for the stylistic difference it'll introduce.

2 Likes
(Matthew Johnson) #53

This argument goes back to the style guidelines holy war. As we have seen, there are a wide range of opinions on that topic.

Personally, I don’t care about consistency from one codebase to another, especially across organizations. Linters will make it easy enough to maintain stylistic consistency within a codebase if the authors desire that and need a tool to accomplish it.

1 Like
(Lily Ballard) #54

I agree with this, though I'd go even further and say I explicitly don't want this for functions. Getters yes, functions no.

1 Like
(Michael) #55

One way we can ensure ease of parsing is by having a common set of rules enforced by the linter.

Being able to implicitly return from functions or getters is not the same as enforcing the rule.

(Frederick Kellison-Linn) #56

Thanks for the thorough reply, @mattrips.

My point here wasn't really to ask the proposal authors to expand the scope of this proposal, but to encourage proponents to think about why we have closure syntax that is distinct from function syntax at all. To reiterate with a (tongue-in-cheek) question that I posed in the pitch thread:

I think that most (all?) people here would agree that there are benefits to at least some of the differences between closure and function syntax, and I think considering why those "inconsistencies" are valuable but the single expression -> implicit return inconsistency is not.

FWIW, I don't agree that this helps readability (except possibly in the case of read-only computed properties and subscripts). That closures are expressions means that they are used inline in other expressions, and point-of-definition is (usually) also point-of-use. Inserting returns in the middle of an expression like numbers.filter { $0 > 5 } .map { $0 * 10 } breaks the conceptual flow of the expression as a whole in a way that simply doesn't apply to function declarations.

In light of my previous point, if we could go back and redesign the closure syntax from the beginning, I would advocate for requiring this change from step 1 to step 2, so that

numbers.map { $0*2 }

would be perfectly valid, but pulling the closure into its own variable would have to be written as:

let double = { return $0*2 }
numbers.map(double)

IMO, this would make the implicit return boundary non-arbitrary ("inline function-like things consisting of a single expression can omit the return, while function-like things used as decl initializers must be explicit"). However, given that this is not the state of Swift today, I'm not convinced the answer is to remove the boundary altogether and allow implicit returns everywhere.

3 Likes
#57

IMO that's where closure starts to get silly, one of the more powerful feature of closure is that its type, and arguments are heavily inferred from context, which is a clear benefits over other declarations/expressions at use point.

We do agree on that, my point is that the consistency argument is quite weak to start another holy war. Though as @Ben_Cohen point out, that's more of a secondary argument.

(Joe Spadafora) #58

This is exactly how I feel about it. I've come around on being okay with this for 1 line computed variables, but still pretty strongly against remove return from any function declarations.

2 Likes
(Matthew Johnson) #59

Consistency of the language and consistency of style are two completely different matters. The argument is based on consistency of the language. I think you're concerned about the consequences on consistency of style. That's legitimate to point out but it does not weaken the argument in favor of improving consistency in the language.

4 Likes
#60

For all the users of Swift that aren't compiler writers, that would seem extremely arbitrary. At least the current rules apply to different constructs -- functions are not closures, syntactically. Getters do look a lot like closures, though, which is why I think there's overall less opposition to relaxing the rules there.

2 Likes
(Frederick Kellison-Linn) #61

The precise wording that I used in my phrasing, perhaps, but I don't think the conceptual difference between "closure/function that has a name" and "closure/function that does not have a name" is very difficult to understand even for non-compiler minded users of the language.

#62

That's still more arbitrary than the current rules.

That would mean

let x = { y * 3 }()

would be valid, but breaks as soon as you change it to

let x3 = { y * 3 }
let x = x3()

It's the exact same body, it's still a closure, but because it's not being called at the point of declaration, it now needs a return keyword. That's arbitrary.

[edit]

I think, in general, that syntax should not change based on how or when something is used. Either one-line closures require an explicit return, or they don't.

5 Likes