SE-0255: Implicit Returns from Single-Expression Functions

I too do this.

Removing the return creates no ambiguity. That holds true even if the single expression is another function, because the return type is expressly declared immediately before it.

func fooOne(x: Int) -> () { view.reset(toPosition: x) }  
// the reset(toPosition:) function's return type must match the Void 
// return type of fooOne(x:); thus, it is Void, and there is no implicit 
// return

func fooTwo(x: Int) -> Result { view.attemptMove(toPosition: x) }
// the attemptMove(toPosition:) function's return type must match 
// the Result return type of fooTwo(x:); thus, it is of return type 
// Result, and there is an implicit return

IMO, these one-liners are well-formatted, easy to read, and appropriate in length.

Most of the ceremony surrounding function declarations (I surmise) is about type-safety and creating an explicit base from which to perform type-inference elsewhere. The function name, the label names, the argument names, the argument types, and the return type all serve the type system.

By contrast, the return statement only tangentially relates to the type system, and does so merely by pointing the parser and type checker to one or more expressions that are intended to match the return type of the function. When there is only a single expression present, if the function has a non-Void return type, then that single expression must be the thing that is being returned and must match the return type. There is no ambiguity, and no loss of explicit type information.

8 Likes

Most of the discussion about excluding functions from this proposal talks about instance methods, but I think nested functions should also be considered. If you want to extract some code into a local helper in a complex function you could write a closure:

func complexFunction<T: FloatingPoint>(…) {
  let score: ([T], [T]) -> T = { zip($0, $1).map(*).reduce(0, +) }
  …  
  let s = score([1, 2, 3], [4, 5, 6]))
  …
}

but you might decide you want to label the arguments and write a nested function instead

func complexFunction<T: FloatingPoint>(…) {
  func score(values: [T], weights: [T]) -> T { zip(values, weights).map(*).reduce(0, +) }
  …
  let s = score(values: [1, 2, 3], weights: [4, 5, 6]))
  …
}
// Error: Missing return in a function expected to return 'T'

Perhaps in future you will be able to label closure arguments (an as-yet-unrealised followup to SE-011) which would either make this less persuasive (“just use a closure instead”) or more persuasive (“there are now even fewer differences between closures and functions but you still can't elide the return from functions”) depending on your perspective. Similar is true of other oft-rumoured features, like allowing closure properties to satisfy method requirements on protocols.

6 Likes

What do you see as the benefit of the fat-arrow syntax for closures? It seems to me that the current closure syntax has a lot of benefit in the case of trailing-closure syntax.

I think this proposal would encourage that style of formatting. Anecdotally, it's something I've seen before in Swift and Objective-C code, and I do it myself. It stands out as a useful signal that says at a glance, "This method is very simply expressed."

1 Like

A reluctant +1. I'm in favor of this proposal although I'll never be truly happy with it: I think a separate concise function syntax is a better way to go. That said, if for the sake of consistency alone, lifting the return requirement should be applied to functions along with properties and the like.

What is your evaluation of the proposal?

+1 for implicit returns in read-only computed properties, -1 for the rest. Overall -1.

Is the problem being addressed significant enough to warrant a change to Swift?

No, I don't think so. Introducing yet another "gotcha" just so we don't have to type return sometimes is not a significant enough of a problem.

Does this proposal fit well with the feel and direction of Swift?

No. It fits Ruby more than Swift.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Yes, Ruby has that feature and I hate it with all my guts. In Ruby, you can't distinguish between functions that were designed to return a value and functions that are void but happen to return a value as a result of an expression. I know this wouldn't be possible in Swift because of strictly specified return type, but still, it makes me wanna cry when I think about that.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I read the proposal.

2 Likes
  • What is your evaluation of the proposal?

+1 for computed properties. I hit this issue almost every time I use them. Everything else is -1, with an overall -1 for everything together. Function declarations already require more formality, so a single return doesn't save much.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Overall I'm not sure, since I don't think the issue is very prevalent for functions, but it would be a nice change for computed properties.

  • Does this proposal fit well with the feel and direction of Swift?

Again, the compute property part feels natural, but it doesn't seem like Swift is tending towards informality in general.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Kotlin, though I haven't used it heavily. I actually find it awkward to switch between the informal syntax when I have a single line function and the more formal syntax when I need to make it multiline, so I don't really like how it feels. But I haven't used it much.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the thread and the proposal.

3 Likes
  • What is your evaluation of the proposal?

+1.

I've always experienced the current solution that makes me write explicit return in one case and not the other puzzling and inconsistent.

Also, I really like the possibility of using the shorthand for functions that are, well, short. I think that the code is easiest to parse when there's some correspondence between the concepts and their textual representation. If the function is short and readable enough to be a single expression, I think it's best to express it in a shortest way possible, which means omitting the return keyword.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Not sure about it. It's not something that I've lost any sleep over during last 4 years. On the other hand, it's just a syntactic sugar that simplifies the mental model a little bit for me.

  • Does this proposal fit well with the feel and direction of Swift?

Yeah, I feel so. It's ergonomics.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I've used and liked it in Kotlin, C# and Scala. It's been always a nice addition for me, a little bit of syntax sugar that just makes things look nicer.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I've read the proposal and the review thread.

  • What is your evaluation of the proposal?

-1

  • Is the problem being addressed significant enough to warrant a change to Swift?

No

  • Does this proposal fit well with the feel and direction of Swift?

No, I feel that we're generally favoring explicitness (and verbosity) in favor of shorthand sugars. Frankly I'm opposed to the current implicit return in one-line closures as well, but I (somewhat) sympathize with the motivations for them. No need to spread them.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Ruby user for many years, where implicit returns for all functions are common.

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study
    Reading.

I'm opposed mainly due to my experience in Ruby, where I had to determine if the thing that I'm calling, and that THAT'S calling, and so on, returns anything. Sure we can look at the return values of those functions, but why NOT just keep it local and look for a return?

I think that this proposal is even worse. At least in Ruby, it worked regardless of the number of lines. In this proposal - want to add a print() call, or introduce a new variable? You've tipped from one line too many. Now you need to add that return.

This is exacerbated bu the existence of the Never, and how it can propagate through implicit returns.

1 Like

Both functions and computed properties must have an explicit return type, so I'm not sure what you're talking about when you mention having to dig through all the called functions to find out what a function returns. Just look right there, at the signature.

That is not a concern. The only form of subroutine which allows type inference for the return type is a closure, where implicit return is already legal. For functions and getters, there is no chance to be confused as to whether a single expression is returning a value. If it's not, the compiler will complain.

This, too, is not a concern. The type of the expression must match the return type. In this respect, nothing is changing. The proposal simply asks that we be able to elide the return keyword. You still have to know what you're returning.

2 Likes

I have a feeling that this is a feature that would be very welcomed by the FP crowd and potentially rather disliked from people accustomed to a more OOP mindset.

Personally, I'm a bit more in the former camp and I generally prefer expression-based languages (if/else not being an expression is another pet peeve...), having mostly written Ruby before (and some JS, where I always hated when I would forget the return keyword; at least, in Swift, the compiler will warn you). But I know other people dislike Ruby's implicit returns.

I would be against this feature being only adopted for e.g. computed properties, but not for general functions. IMHO, that would create an annoying inconsistency, and I would rather prefer not having the feature at all than having it in that way.

I'm also somewhat intrigued by the func foo(x: Int) = x * x syntax proposal; I think Scala does it like that?

3 Likes

You are probably right.
I am from the latter camp and I really dislike this proposal, at least for the case of functions.

I find that code readability would really suffer from the omission of return (at least in the case of quickly glancing through a piece of code).
I’ll take the sum function used in the motivation section of the proposal as an example: in its current form, I can just take a quick glance at it, reading only the beginning of each line, and I will know both its name and that it returns a result.
With the omission of return, the process is noticeably longer, as explained in the proposal itself:

When reading the implementation--after the var keyword and name--you first encounter the return type and then the single expression within the body. Since you've just read the return type, can see that there's only one expression in the body, and are told by the compiler that the code is legal, you are forced to conclude that the expression must indeed be returned.

That sounds like a lot to do in order to reach the same conclusion that seeing the return keyword would bring me to.

Another annoyance is that not all expressions read like expressions right away. Some look more like a statement when taken in isolation. It is interesting that the proposal uses precisely an example of such an expression (reduce) for its motivation section.
There again, I find the return keyword to be useful for making things explicit and forcing what follows it to be read as an expression.

And of course, all this will be made worse when going through a mix of code using return and code that omits it.

It does not seem to me that the gain of saving 7 keystrokes is worth it.

I suppose I belong to the OOP crowd. In my personal experience, I'm mostly exposed to the omission of return with the Ruby language. My daily job is mostly spent writing Swift and a little ObjC, and sometimes I enjoy a little Ruby.

Each time I switch language, it takes a few minutes or hours until I become fluent again. It happens that I add "useless" returns in Ruby (that I remove later, usually during code review), or omit "required" returns in Swift (that I add right-away because, you know, compiler).

Based on this experience, I do not think that the presence or absence of return is part of the language identity, or any OOP/FP "affiliation". To me, it's part of the developer's muscle memory. And muscle memory is highly malleable.

On the other hand, elision of return in "single-line code chunks" is currently part of the language identity, thanks to closures, and I appreciate a lot that the proposal respects this.

2 Likes

And as someone from the former camp, my main issue with the proposal is that it doesn't go far enough. I'd love to see all returns eliminated (though I understand why that won't happen).

That process isn't really any longer; you just take a quick glance at just the first line and you know both its name and that it returns a result [well, really, you know the type that's returned, as no return type indicates a return of type ()].

I.e. for this example, I look at that function, see -> Element and know that it returns something of type Element. You don't even need to see that it's only one line to know that it must return something, and even with explicit returns, you still have to rely on the lack of errors to know that someone didn't try to return something of a different type than is indicated.

I suppose this is just another example of having soaked in FP/OOP for a long time. I see reduce and think expression, where you see it and think statement. I may very well have used reduce in the example myself as I would no more think that was a statement than 1 + 1.

So if you were to come across something like:

func boo(_ name: String) {
    return print("Howdy, \(name)")
}

Would you really read the print statement as an expression there? Would you mentally treat that code differently just because the return is in there? Because the compiler doesn't treat it any different.

The clarity, at least for me, is in the signature of the function, and not the presence or absence of the return keyword.

func foo() -> Bar { bar() }

Using a linter to encourage consistency will solve this problem in a team environment.

1 Like

Asking the compiler to help me write less words is also why I am in favor of this proposal.

Upon further reflection, this form deserves consideration as an alternative to this proposal. Has it been pitched previously?

1 Like

Could this syntax (from @Chris_Lattner3) be extended to computed properties as well?

var sum = reduce(0, +)

// instead of
var sum: Int {
    return reduce(0, +)
}

It would be very elegant, especially if we could omit the type just like we do for non-computed properties, thanks to type inference:

var count = 0
var sum = reduce(0, +)

Thinking of my own humble SQLite wrapper, this would greatly enhance the definition of record associations:

// Currently
extension Author {
    static let books = hasMany(Book.self)
    var books: QueryInterfaceRequest<Book> {
        return request(for: Author.books)
    }
}

// Future?
extension Author {
    static let books = hasMany(Book.self)
    var books = request(for: Author.books)
}

// Enjoy
let author: Author = ...
let books = try author.books.fetchAll(db) // [Book]

A very passionate -1.

No. I do not believe this proposal addresses any problems. In fact, I believe it causes harm.

This proposal assumes an implicit return is somehow "better" or more clear than an explicit one, yet it does not provide any evidence to back this. From my own experience, as someone who teaches programming for a living, unnecessary sugar makes programming languages harder to use and understand. In particular, I would like to speak out for my many, many students who fall into one or more of the following categories:

  • chose to study computer science because it seemed like an interesting profession with lots of job opportunities, but don't consider themselves "a geek",
  • have some form of autism (as a trait, not a disorder),
  • have a very mathematical, analytical mind.

These students tend to appreciate explicitness. Whenever they encounter sugar, they tend to remove the sugar in their mind so they can better understand what's going on. This means unnecessary sugar increases the cognitive load of reading code, which is the opposite of adding clarity.

Just to give you an example, here's the cognitive load required to process the motivating example in the proposal:

func sum() -> Element {
    reduce(0, +)
}
  1. "OK. So this function calls some other function named reduce."
  2. (student recalls the design guidelines) "This reduce function is named using an imperative verb, so it does something and has side effects." (student now assumes sum simply calls reduce)
  3. "Oh wait, there's a return type." (easy to miss as it's and the end of the line)
  4. "So what does this function return?"
  5. "Oh right, there's this exception where return can be omitted if it's only a single line of code. This must be one of those cases."
  6. (student looks up reduce) "Ah, reduce does return something instead of having side effects. Weird, that must be an exception as well."

In this example, the return keyword plays an important role in helping the student understand what's going on. Removing it makes it much harder to understand.

So far, the core team has done a really good job at carefully selecting which sugar to include and which not to, but I don't feel like this proposal meets the standards the language has set so far. I understand where the proposal is coming from, and that it looks like an obvious improvement to many, but I don't feel like it solves a real problem and it has the potential to reduce clarity instead of improving it.

In general, I would also like to ask the community and the core team to take the utmost care when it comes to accepting new sugar. I don't see many proposals backed by studies that prove the proposed features do indeed improve the language for the general public (of which this forum is probably not a good representation). Without proper evidence, I don't think marginal gains are enough for a proposal to be accepted.

6 Likes