Require parameter names when referencing to functions

I'm fine with it. And the author of the method has full control - if they want, they can call it firstWhere instead - nothing in the language precludes it. My understanding is that Swift simply choses to, by convention, express method similarity more explicitly than merely a common string prefix, but with also with an explicit '(' terminator on that prefix.

There already has to be some way to disambiguate e.g. first(where:) and first(after:) - the compiler can't implicitly refer to both as merely first. Disambiguating consistently seems like a readability win to me (and lessens vulnerability to API changes).

(granted this hypothetical first(after:) is fairly esoteric, but it's technically valid, functionally distinct, and cannot be disambiguated based on parameter type, which is (T) -> Bool in both)

That all said, if the compiler could disambiguate based on the presence of the trailing closure - between a single-parameter first(where:) and a zero-parameter first - that'd be an interesting avenue. Though it seems like that could be ambiguous when used in an if statement.

4 Likes

The question is: does this hold true in ~all such cases? It seems like this is relying on an assumption / assumed rule that any 'dangerous' 'overloads' deliberately use a distinct [full] name. Which seems like a good idea irrespective of this thread, since otherwise actual invocations of the method could likewise unwittingly using the dangerous version by accident, but have method authors actually been that careful to date?

3 Likes

It seems to me like we have five options proposed here:

  1. Status quo
  2. Require parens and parameter names for every non-nullary function reference
  3. Require parens and parameter names only when required to disambiguate between a property and a function, or multiple functions with the same base but different parameters
  4. Require parens and parameter names if there is a single parameter labeled with words but not when they have only underscores (so max would be exempt but contains(where:) wouldn't be)
  5. Syntax for providing an alternate name, available only for references but not callers

It seems like both 4 and 5 are workable, with 5 being a little more challenging to implement?

1 Like

Isn't this just the status quo?

Not quite. In the world of option 3, if you tried to use let func: ((Thing) -> Bool) -> Int = myArray.count, that would be a warning or an error (because it's an ambiguous reference), which is different than the current world.

Jordan brings up a few problems with it though:

2 Likes

I thought about this problem some more and came up with the following rule that I think would greatly improve readability of functional code in Swift:

When referencing a function, all of its external parameter labels must be listed.

With this rule in place, functions like + or max can be used ergonomically as they do not expose any external parameter labels. All the other functions that expose external parameter labels expose them for a good reason and so they should always be listed at the point of use.

11 Likes

Waking up that thread because I'd love to see progress on this.

Got hit by that problem today while trying to reference a #selector:

class MyViewController: UIViewController {
  ...
  @objc func validate() { ... }
  override func viewDidLoad() {
    super.viewDidLoad()
    let doneBtn = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(validate) // error
    self.navigationItem.rightBarButtonItem = doneBtn
  }
}

That code errors, telling me that #selector(validate) is ambiguous because it could match either MyViewController.validate() or UIResponder.validate(_:)

Making the change suggested in this thread that all functions with parameters should reference their parameters explicitly would solve this. If we make an exception about "only external parameter labels must be listed" like suggested by @salutis in the message above, that would mean that because UIResponder.validate(_:) doesn't have an external label for its parameter, the ambiguity would remain though…

I'd love if we could find a solution so that all parameters have to be explicit when referencing a function. Maybe the only exception we could accept is for operators (like +) but not for anything else (I'm fine with having to reference max(_:_:) instead of max for example)

2 Likes

How about specifying parameter names as well as their types? That seems to be the only resolution to the ambiguity brought up by @wadetregaskis in response to @beccadax.

I very much like this proposal.

However, wouldn't this syntax change be source breaking?

This is clearly a big hole in Swift as it hinders Swift's expressivity and introduces tons of ambiguities in its current state. Though this is a breaking change, I only foresee it causing more problems in the future, so I hope this gets changed sooner rather than later.

7 Likes

@soroush Did this ever get a proposal?

Sadly, no. I feel like we didn't really make any decisions about which direction the proposal should go in, and it kind of fell off my radar. Happy to work on a proposal if we have a direction in mind!

I think it's definitely time this is resurrected and implemented. One should be required, as suggested, to use the full name of a function (e.g with its labels) when referring to a function -- with no chance of it being returned to its current state.

All of them, or just external argument labels (so, excluding _ names)?

I think everyone had agreed on foo(_:bar:). The only disagreement was on when, if ever, foo would be allowed.

foo on its own should never be allowed unless there are NO parameters.

func foo() must be referred to as foo
func foo(bar: Int) must be referred to as foo(bar:)
func foo(_ bar: Int) must be referred to as foo(_:)

I think the best thing to do is vote for one of the 5 options above. @austintatious, it sounds like you’re a hard 2?

1 Like

Is there no exceptions for operators and functions without external parameter names? There was much derision (including from myself) for forcing ugly map calls.

1 Like

Two (three) cents from me:

Functions without arguments could be written with a Void argument (just like Void is used for return values):

foo(Void)

If you leave out the comma, wouldn’t it be okay to leave out _, too?

let x = array.reduce(0, max(::))

Shouldn’t the return type be part of the syntax?

func foo(_ x: Int) -> String { "\(x)" }
func foo(_ x: Int) -> Int { x*2 }

let a : [Int] = [ 1, 2, 3 ].map(foo(:))   // Should have to be: .map(foo(:)->Int)

Swift isn't C. The "myFunc(void)" syntax from C(++) is a legacy irregularity. It was required because "myFunc()" was (kind of) the same as "myFunc(...)" in ancient C! And void isn't an actual type that can be used for objects.

But in Swift, Void is a full type with instances, and said instances can be passed around. So a function that can take a single argument of type Void is a legitimate signature. Since we have regularity, we represent zero arguments by actually having zero arguments.

5 Likes
Terms of Service

Privacy Policy

Cookie Policy