Allow function definitions to omit parentheses if no parameters

What about allowing a function or method definition to omit parentheses if there are no parameters?

Simply instead of this:

func myAction() { ... }

Allow this:

func myAction { ... }

This is only when defining the function, it would be called as usual with parentheses.

3 Likes

Other than being inconsistent, what's the value?

17 Likes

Even if no arguments are passed parentheses have a function as the Function call operator at the call site but when defining a function it might be cleaner to only have parentheses when there are parameters to the function.

Take for example a function that returns nothing does not have to explicitly define the return type as Void:

func close() { ... }

Is slightly nicer on the eyes than:

func close() -> Void { ... }

This is just an extension of that logic, just for parameter lists instead of return types for functions.

2 Likes

My initial reaction was also “what’s the point?”, but, technically this would make things in the language actually more consistent given how read-only properties are defined today:

var myProp: Int {
  return 5
}

func myFunc -> Int {
  return 5
}

I’m certainly not clamoring for this simplification, but I see no principled objection to it.

Though the obvious tangential question is: why use func in such cases instead of just var? Essentially you’re just dictating how conveniently a user can invoke said code:

print(instance.myProp)
print(instance.myFunc())

In Objective-C subsequent to the introduction of dot-syntax there were conventions laid out on when to use one vs the other, which was immensely helpful but didn’t resolve all ambiguity. Swift has a similar problem today, but to my knowledge hasn’t established any guidance or conventions? It’s certainly the wild west out there in my experience, w.r.t. this.

So the crux of the matter seems to be: does it matter to the caller whether they have to append () or not? Is it communicating anything useful to them?

7 Likes

Arguably though, these two things aren't what should be made consistent. Properties are already consistent within themselves between how they are declared and how they are called (without parentheses). Likewise, with the exception of the trailing closure case, parameterless functions are already consistent within themselves between how they are declared and how they are called (with empty parentheses).

Removing the empty parentheses from a function declaration makes the language less consistent, not more, unless you propose dropping the empty parentheses from calls as well. Some languages do in fact do that (Ruby comes to mind), but that syntax is already used in Swift to produce a reference to a function. This ambiguity could partially be resolved using inference—the type of a function cannot be the same as its own return type, so the expression f(someFunction) could be resolved to either the result of someFunction or the reference to someFunction based on the type of f's argument—but even that falls apart if f's argument is Any, and that would be introducing a source-breaking ambiguity.

16 Likes

Why not just use the read-only computed property?

1 Like

It's worth noting that Ruby doesn't just drop parentheses, it goes beyond that. Ruby is object-oriented to the point that everything you do on an object is a message that you send to it. Even if it looks like property access, you are actually sending the object a message, and the object can handle that message either by implementing a method or intercepting it with some meta programming.

Swift's compiler enforces a syntactic distinction between properties and methods without argument, but since there is little semantic clarity on when you should use which, and since with computed properties you already have something like a function, this seems like a distinction that maybe wouldn't have been necessary to make.

Yes as @allevato pointed out a function name by it's self is a reference to the function it's self and changing the semantics on that would be a breaking change which has little to no merit. The function call operator rules would still be left as is, myFunction would reference the function it's self and myFunction() would call it.

The only thing new I am proposing is the option to omit parenthesis when defining a function that takes no parameters just how you can omit writing func myFunction() -> Void { } when the function does not return anything.

The API Design Guidelines suggest that computed properties should be restricted to O(1) as much as possible. While it doesn’t actually say so in the guidelines, parameterless functions are often used to indicate that what could otherwise have been a computed property is more complex than O(1). Compare Array’s sorted(), randomElement(), and max() vs isEmpty, count and first. It isn’t a perfect rule—the need for a setter can force a computed property—, but it is an intentional design pattern.

9 Likes

I'm really glad that some people took a look into this thread and I really am grateful that the participating members took the time to entertain this idea.

I thought it would be easier to flesh the idea out in a draft proposal here:

The most important part of the proposal is this section:

Proposed solution

func definitions

Allow functions that take no arguments to be defined as follows with no parentheses:

    func foo { 
      ...
    }

Void computed properties

Currently it is legal to write this:

class Foo {
    var myProperty: Void {
        print("Why do I exist?")
    }
}

This should not be allowed by the compiler, it serves little to no purpose.
Computed properties should be O(1) complexity and return something,
anything else belongs in a function.

I know Swift is opinionated, but I think disallowing a specific type for (computed) properties is going too far. What if Foo was generic, and the caller specified Void as the type?

7 Likes

In the proposal:

Function definitions in swift allow omitting -> Void as the return type for functions that return nothing.

That's false. They return a Void. You can type this in a playground and it's legal:

func f() {}
let x: Void = f()

What you're actually trying to say is this pattern (-> Void) is what we use for functions that otherwise have nothing to return. That's not quite the same thing.

In the proposal:

() is also Void as well

That's also not true (not true any more, AFAIK, but if I'm wrong someone will jump on me). It's not a value or a type at all, but just a piece of syntax that encloses function parameters. If you want to let it be omitted when there are no parameters, that's a rational proposal, but it's got nothing to do with Void.

In the proposal:

I believe that this would make the language more consistent

Again, this is the wrong justification, since you've already seen upthread that different people care about different kinds of consistency. Your actual justification is that the empty () is not of great importance when present, and not a detriment if omitted.

Personally, I expected to be neutral or against such a change (because it seems pretty trivial), but when I look at your example:

    func foo { 
      ...
    }

it actually looks pretty fluent to me. Unless someone can find a technical objection, it's something I'd actually support, I think. The proposal's motivation just needs to be more robust.

(Plus, I strongly agree with @Avi that you should not address Void properties in this proposal.)

Thank you for keeping my logic in check.
I agree, that is way to extreme so I removed it from the proposal. I stripped it down as much as I could to the point where its just: “It would be nice to be able to write: ‘func foo {}’ instead of ‘func foo() {}’

@QuinceyMorris Very solid point but I meant omitting the need to actually write ‘-> Void’ when it is implied.
I meant that the return type is still ‘Void’ just that the programmer defining the function is not required to state the obvious by explicitly writing ‘-> Void’ when it can be inferred.

Same thing with ‘()’ I think they are just noise when the function has no parameters. I updated the proposal and stripped it down a lot. If you have time mind checking it out again? I really appreciate your feedback.

I think I'd say it something like this:

Swift already allows syntax to be omitted when it's judged non-necessary for clarity. A good example is the closure parameter syntax. In its full form, it looks something like this:

// assume this function definition:
func foo(completion: (String, Int) -> Int) { }
// then a closure can be written like this:
foo { (s: String, i: Int) -> Int in
   return 0
}

Various parts of this can be omitted, in various combinations:

foo { (s: String, i: Int) in // omitted return type
    return 0
}

foo { (String, Int) in // omitted parameter names
    return 0
}

foo { (s, i) -> Int in // omitted parameter types
    return 0
}

foo { String, Int in // omitted parentheses
    return 0
}

foo { s, i in // omitted parentheses
    return 0
}

There's a good argument here that the briefer forms are clearer than the full form, but the amount of brevity is a matter of taste, and no one is forced to use any particular form.

A similar brevity+clarity argument allows us to omit a Void return type when a function is declared:

func bar(s: String, i: Int) -> Void {  }
// may become:
func bar(s: String, i: Int) {  }

In this case, omission of parameter names or types isn't possible, nor is omission of parentheses generally desirable. However, when the function has no parameters and returns Void, omission of the empty parentheses seems like a reasonable option:

func zed() -> Void {
   …
}
// may become:
func zed() {
   …
}
// then could become:
func zed {  
   …
}

The last two forms seem equally readable. Therefore, this proposal adds the flexibility of omitting the empty parentheses in such cases.

You might also want to consider whether this is allowed:

func zed -> Void {
   …
}

or this:

func pom -> Int {
   …
}

or this:

func wat throws {
   …
}
1 Like

I'm a definite +1 on this.

The only thing an empty parameter list does is duplicate the indications that it's a function and that it doesn't take any parameters. Not a monumental change by any means, but IMO a welcome improvement and it (naively) doesn't seem like there would be any source/abi stability issues. Though I'm guessing the same wouldn't be the same for source code processing. :grimacing:

Are there any reason not to also support this for functions with a non-void return?
e.g.

func foo -> String { //do something }
// instead of
func foo() -> String { //do something }

The () serves no real purpose here either and IMO the code is just as clear without it. while being more concise.

P.S. interestingly the way Swift's syntax differentiates between properties/functions/closures seems to enable this with clarity as compared to other languages I've used. Almost like someone was eventually planning on doing something like this.

Also, for those saying you can just use a computed property, here's why that doesn't make sense:

  1. It's not passable in the same way.
func doAThing(with aFunc: () -> (String)) { 
    print(aFunc()) 
}
var aThing: String { "A Thing" }
func something() -> String { "Something" }

doAThing(with: something) //prints "Something"
// Can't pass this naturally
doAThing(with: aThing) //Cannot convert value of type 'String' to expected argument type '() -> (String)'
  1. Void properties seem weird
// What is this even for if it doesn't return a value?
var aThing: Void {
    get {
        
    }
}
  1. Generally speaking (yes there are exceptions), a Function/Method is a verb and a Property is a noun.

e.g:

Foo {
    var updateDatabase: Void
    func fullName() -> String
}

foo.updateDatabase
foo.fullName()

These are grammatically weird, whereas.

Foo {
    fun updateDatabase() 
    var fullName: String
}
foo.updateDatabase()
foo.fullName

feels much better.

@GetSwifty Awesome feed back! Thanks for Chiming in!
@QuinceyMorris Good point! I updated the proposal with examples and added a bit to the motivation section.

@GetSwifty I fleshed out the proposal a little more, how could we request a review?

Here is the latest version of the proposal: