Pitch: Implicit Returns from Single-Expression Functions

For cases where closure is specifically expected to return clearly defined value (or Void), this pitch might be fine, but for cases where return value is more ambiguous (e.g either void or value), there might be situations where developer accidentally uses non-returning variant of function when developer meant to return value. In those ambiguous situations it’s better to always include ”return” when value returning function is used, so that code is more easily understood

EDIT: My point above is slightly misguided, as closures already allow the shorthand syntax, so this is only about functions.

However, I see closures overall as shorthand (also including the special parameter syntax), and functions as the more fully formed syntax. I think it's good that they are separate, and don't see need for functions getting a shorthand syntax like this.

Could you provide a concrete example illustrating the scenario you're concerned about?

This isn’t a bad proposal, but I find myself wishing for implicit return more often on single line computed variables than I do on actual functions.

7 Likes

Yeah, that's my common use case too, and the one I'm most happy about in this proposal. Applying it to regular functions is more of a "sure, I guess it's good to be consistent" thing for me.

8 Likes

Would it make sense to explicitly exclude types Void and Never from this proposal? If feasible, that would simply avoid the edge cases where the intention might be unclear (and would be 100% source compatible).

Limited to only single expression functions, maybe this wouldn't be too confusing. That said, I like the last alternative best, but with a further limitation of just local functions only (does limiting the scope help with type inference?) and with a fat arrow. That would give programmers a real one-liner:

func multiplyByTwoAndThree(a: Int) -> (Int, Int) {
  func multiply(b: Int) => a * b
  return (multiply(b: 2), multiply(b: 3))
}
1 Like

The Never case isn't about a function or variable that returns Never, but rather something like this:

var foo: Int {
  fatalError("I'll get around to implementing this on Sunday")
}

That's something we can't break, but syntactically it looks exactly like the usual implicit return.

4 Likes

Looks good to me. I’ve always found this inconsistency a bit odd and occasionally wished I didn’t have to write return in these situations.

3 Likes

This is similar to what Perl does. The value of the last statement executed becomes the return value of the block.

I think this looks really great and I'm looking forward to have this in Swift. Since you mentioned Scala in the last paragraph there is something else they allow which I think would be a big benefit for the proposal: have the expression after an = symbol instead of curly brackets.

This would make it much more obvious that this is the value that gets returned and makes it distinguishable from the current syntax.

So I propose to change the syntax from

func functionName(parameter1: Par1, parameter2: Par2) -> Ret {
    expressionThatReturnsRet()
}

to

func functionName(parameter1: Par1, parameter2: Par2) -> Ret = expressionThatReturnsRet()

Function bodies with {} would still need the explicit return keyword. This syntax could also be forwarded to other occasions like subscripts or get/set:

var foo: Foo {
    get = _foo
    set { _foo = newValue }
}

From the top of my head I know that Scala and Kotlin are allowing this (Haskell as well kind of). What do you think?

EDIT: Just saw that @Francois_Green proposed pretty much the same in Pitch: Implicit Returns from Single-Expression Functions - #8 by Francois_Green. :+1:

3 Likes

I like the change Benjamin did here.

In the original pitch it's very hard to read what exactly is returning. When you are new to the language you have no Idea what's going to happen here. Especially if you don't have an IDE available.

2 Likes

Excuse me? Can I ask why my proposal of the exact same thing was pushed back so hard especially by @Chris_Lattner3 back then? I appreciate all the work that author has done on this proposal but he missed the historical research on why this was not pushed forward so far.

By this comment this feature was simply put onto the bookshelf for many many years until Swift evolved so much that there is not much to add except convenient sugar:

The same proposal long time ago, before an implementation was required:

To be clear with everyone, I'm not trying to flame here nor do I put a brick in front of this proposal, as I proposed it myself already a few times, but I'm a little bit speechless and as I mentioned above already I appreciate the work from the current author.


Other than that, I'm +1 on this.

9 Likes

+1 from me! Ben Cohen's tweet nails it: https://twitter.com/AirspeedSwift/status/1108902065634328581

Although I like this aesthetically, and for its consistency with other languages, there's an ambiguity hazard here because many introducer keywords in Swift are contextual, including get and set. This is already valid, albeit unlikely:

var get = 0
var foo: Void { get = 0 } // a get only computed property
6 Likes

I'd actually be -1 on this as currently pitched.

Ruby allows implicit returns everywhere and I find it to be very confusing so I always am explicit in my returns anyways (coming from C/C++, python, and swift).

I do like the suggested alternate syntax that requires expressions in {} to still use return while declaration = expression can be the "implicit return" expression. This eliminates my confusion of not seeing a return and wondering why something is being returned anyways. There is no doubt about what the = means in my mind.

1 Like

I think this looks great. The nice part is that the new contexts that this expands skippable return to always have types specified explicitly, so there's much less chance for any kind of ambiguity.

I will say that I hope we can provide good error messaging / fix-its for the case where someone adds an additional line to a function using this feature. Perhaps if the compiler runs into code that doesn't have a required return statement, it can look to see if the final statement in the function body matches the function's type and offer a fix-it.

4 Likes

Great idea! Added that diagnostic here: https://github.com/apple/swift/pull/23251/commits/cfbd96ad1361e31287b51336694e26ed9865b231

 

Now

public func hi() -> Int {
    print("entering hi")
    17
}

gets the more helpful diagnostic with a fixit

> debug-swift test.swift
test.swift:3:5: warning: integer literal is unused
    17
    ^~
test.swift:3:5: error: missing return in a function expected to return 'Int'; did you mean to return the last expression?
    17
    ^
    return
11 Likes

FWIW, I still feel exactly the same way about this, and I find that the "just omit the return" proposal to be super problematic from a clarity perspective.

The func f() -> Int = 42 variant is a significant improve on that clarity issue, but I still don't think it is "worth it" to add complexity to the language for this.

-Chris

9 Likes

Nice! That looks like a great improvement independent of this new language feature. :+1:

How about just for computed properties?

1 Like