Allow function definitions to omit parentheses if no parameters

I feel that the change would make func too similar to var.

Right now the declaration signifier

  • For func is func, -> and arguments
  • For var is var and :.

Since arguments is generally the most blatant signifier, people (aka, me) do subconsciously try to locate it first. Removing () would make it confusing visually as to whether or not it's a func or var.

11 Likes

I do agree with @Lantua.

Even if this is optional, I'm afraid new comers to swift might get confused and/or tempted to use everywhere paving the way for, IMHO, poorly written code.

2 Likes

It's worth noting that Void is a type that actually does have a value that you can compute and return here, it's just spelled (). There's absolutely no reason we should ban this. Maybe warning about it (and even that's a stretch), sure, but this makes more sense as a linter warning.

4 Likes

You're not omitting parameter names. You're still omitting parameter types, with strangely named variables String and Int

1 Like

I'm sorry for not speaking up earlier, but I think it's very unlikely that this proposal would be accepted, and I'm not sure it's worth your time to work on it.

22 Likes

Do you have specific examples in mind? What kind of poorly written code would this enable?

If anything I would expect this to make it easier for new programmers to pick up, as the parameter list has a distinct purpose rather than functioning as an implicit requirement even when it serves no purpose.

Whenever there is more than one way to express a particular construct, especially when both ways are mere stylistic preferences instead of actual simplifications, the natural question for a beginner to ask is "why?". When language designers simply answer back "because we could", it doesn't present an expansion of possibility the way you might imagine, it presents a pedagogical roadblock. Instead of "here's two ways, pick the one you like", it's "oh great, there's two ways to write this, and I have to remember them both". This may seem to trivial to you and I and the others in this thread that have been using Swift for years, but increased choice is increased complexity and increased complexity increases avenues for mistakes, confusions, and misunderstandings as much as it presents opportunities. If a proposal cannot overcome a test as simple as that, I don't think it merits inclusion.

Y'all keep citing that this or that bit of syntax "serves no purpose" or that it "means nothing" or that certain constructs around this "ought to be banned" all of which have been stylistic judgements rather than semantic judgements. Consider that being explicit is a virtue in this language. One person's "syntactic noise" is another person's "declaration of intent".

32 Likes

IMO, those two things are hugely significant.

5 Likes

Unfortunately, your argument is undermined by Swift itself.

Some examples:

  1. Readonly computed properties can nest a get { } block or write the code in the main block of the property definition.
  2. Trailing closure syntax is optional.
  3. To add insult to injury, for a function with only a single closure parameter, the empty parentheses following the function name are optional!
struct S {
    var x: Int { return 0 }
    var y: Int { get { return 0 } }
}

f(block: { _ in })
f() { _ in }
f { _ in }
2 Likes

Isn't that a bit of a contradiction?

Anyway, there are numerous areas in Swift where how to write something has been left up to the user, the worst offender probably being Closures. Seems like a bit of a straw man.

Single statement functions were just allowed to omit return.

Not to mention Void returns, void closure parameters, inferred types everywhere, etc.

If it's your personal philosophy to make everything explicit as possible, go for it. However Swift has generally gone in the direction of brevity without sacrificing clarity. If you want to argue func doThing {} is meaningfully less clear than func doThing() {}, please do.

1 Like

Here is how I feel about the examples you have proposed.

  1. The difference here is that as a consumer of the properties on struct S you don't see a difference they are just property x and y that are read only.
  1. This increases readability the vast majority of the time. If you have a function where the intent of the closure isn't clear then it hurts it and I would argue that is more API design more than a fault of the languages rules around trailing closures.
  2. It is still clear that this is a function. Because there is no if, switch or other code around f.

These small "inconsistencies" can be quite difficult for new comers to the language to grasp. Adding more that doesn't in any way increase readability or intent, in my opinion, is going to make that worse. These kinds of things were a major hurdle for seniors in the university iOS course I helped teach in the Spring.

To me ()s means that this is a function and since it needs to augments it must need to do a lot of work. If this were a computed property it should be O(1) from at least the second access on.

That's subjective...if it was added and you think it's less clear, you don't have to use it. That doesn't mean it shouldn't be available as an option, just like numerous other places in the language.

What's the argument why this is different than all the other cases where Swift does this?

That is why I qualified my statement with "in my opinion".

To me and most Swift devs I know it denotes the amount of work. () mean there is always work and a computed property means that there is either no work or only work on the first access.

1 Like

It's good that you brought this up. Let's see a few reasons why these work as simplifications, as opposed to this proposal

  • Readonly computed properties can nest a get { } block or write the code in the main block of the property definition.

Consider why this is

var x: String {
  get {
    return myStringComputation()
  }
}

Introduces a read-only computed variable with two scopes when only one suffices. Especially in the context of aggregates, read-only computed properties are a very common case that it makes sense to optimize for. The semantics of this shortened choice have a clear and logical explanation for their inclusion: redundancy in the common case can be eliminated by casting explicitness aside for clarity. In addition, there is no semantic ambiguity. The getter is pretty much the only choice you can make here as it doesn't make sense for this to be an observer or a setter.

var x: String {
  return myStringComputation()
}
  • Trailing closure syntax is optional.
  • To add insult to injury, for a function with only a single closure parameter, the empty parentheses following the function name are optional!

Expressions and declarations are distinct syntactic categories, though I can see why this could be confusing. A better place to look for a counterexample would be the protocol conformance synthesis machinery that we have in place, or even the initializer inheritance model that we have. There, the compiler automates away boilerplate declarations whose definitions really are just noise, and can even be hard to get right by hand (see, memberwise initializers for large aggregates). In either case, the point is that the language has optimized for a particularly common mode of use, not just a stylistic choice. It can be a subtle distinction at times, but I doubt you could argue omitting () is a common mode of use. And it's illogical, as if we harken back to our declaration-follows-use ancestry in expression context here you would get a declref pointing to a curried closureinstead of the computed value of executing the final layer of closure as others have pointed out.

7 Likes

Generally I would agree. How does that affect the proposal?

1 Like

If a function takes no arguments and you can simply call it as let bar = Foo.bar instead of let bar = Foo.bar() you will expect O(1) behavior when it likely isn't.

1 Like

I should also add that I see this as the next logical proposal if people can write func bar { ... }.

It just seems like a slippery slope for the same reason the core team has been fighting against sugar lately.

1 Like

I invite you to quote where in that reply I said "everything must be explicit". There is absolutely noise that can be omitted in a modern programming language. Type reconstruction allows for redundant annotations to be stripped (see Java), trailing closure syntax facilitates the common case of callback-style APIs, closure shorthand syntax optimizes for the common case of a single-expression closure (cf collection APIs).

However Swift has generally gone in the direction of brevity without sacrificing clarity

In limited cases. We have abbreviations for common patterns, as I have articulated in a few cases in the previous reply, but arbitrary shorthand has not yet made its way into the language.

Of course you can't argue that when it's not possible...? You could say the same about ommited returns. Why is it illogical?

Isn't an empty parameter list a common pattern? Why do you see this case as so different? What am I missing here?