Require parameter names when referencing to functions

The answer is no. Trailing closure syntax is part of call syntax, not referencing syntax. That doesn't mean it can't be revisited later, but it's not tied to this discussion.

7 Likes

Thanks for clarifying, that's what I guessed would be the case but wanted to double-check.

With that in mind, I think Jordan's 'middle ground' strategy is acceptable — argument labels have semantic value, but it seems silly to need to write _ (one or more times) when all arguments are unnamed.

1 Like

We do have support for this in code-completion, but it could use some love. Currently it only works on non-generic function types.

3 Likes

This example backfires on itself. Let's consider an example that's very close to yours, but without the "objectionable" omission of parameter keywords:

a.map(String.init(_:))

Here's the corresponding list (culled from your greater list):

init(_ c: Character)
init(_ cocoaString: NSString)
init(_ content: Substring.UnicodeScalarView)
init(_ scalar: Unicode.Scalar)
init(_ substring: __shared Substring)
init(_ unicodeScalars: String.UnicodeScalarView)
init(_ utf16: String.UTF16View)
init(_ utf8: String.UTF8View)
init(_ cocoaString: AnyObject)
init(_ sel: Selector)

You can soak in that list too, and realize that the problem you're pointing out is as much about the inscrutability of type inference as it is about the ambiguity of omitting the labels.

5 Likes

You say that as if it's a settled matter that this is problematic. @jrose suggested that this is a problem, but didn't provide any justification for why it's "harder".

It seems arguable that this is no harder than (say) changing the spelling of a keyword on an existing method. In both cases, the outcome is a compilation error or warning that didn't occur previously. In both cases, the library author may consider whether the upheaval is worth the benefits of the change. But it's not obvious that one change is "harder" than the other.

(This is under the assumption that adding the second method does in fact produce a compilation error.)

1 Like

Your list is incorrect. init(_cocoaString:) and init(_sel:) have labels with leading underscores, not unlabeled parameters; you also missed init?(_ codeUnits: Substring.UTF16View) and init?(_ codeUnits: Substring.UTF8View) from later in the list. That leaves:

init(_ c: Character)
init(_ cocoaString: NSString)
init(_ content: Substring.UnicodeScalarView)
init(_ scalar: Unicode.Scalar)
init(_ substring: __shared Substring)
init(_ unicodeScalars: String.UnicodeScalarView)
init(_ utf16: String.UTF16View)
init(_ utf8: String.UTF8View)
init?(_ codeUnits: Substring.UTF16View)
init?(_ codeUnits: Substring.UTF8View)

This is a list I'm quite comfortable with. Every overload here straightforwardly converts something that's already very string-like to a string. None of these overloads are implementation details or security risks.

10 Likes

That's true, but not the "harder" I was getting at. I probably should have phrased it as "no easier than today" though.

I think people understand and agree that you shouldn't change the name of an existing API, at least not without thinking carefully about it. And people understand and agree that it should be okay to add a new API with a new name. So the murkier areas are

  1. adding a new overload-by-type to an existing API, which should be considered carefully, and which this proposal does not really make any more or less safe

  2. adding a new API that has the same base name as an existing API but different argument labels (ignoring default arguments for the time being)

I am pretty strongly of the opinion that (2) should be as safe as adding a new API that has a different base name, and that's what this proposal addresses.

15 Likes

I'm very much in favor of this. I've run into this issue many times when naming a function succinctly but I have a property that starts with the same name. It's really frustrating and makes me have to compromise my method name or property name. I think the requirement to specify the method fully is more precise as stated and resolves all of these issues. Big +1 from me.

4 Likes

I was initially +1 on this, but reading through the negatives I'm a firm -1. It would be a huge breaking change to existing code, and it ruins elegant syntax like foo.map(fn).

I really wish we could solve problems like let foo = foo(arg), but it seems like that can be tackled by improvements to type resolution, without requiring any further disambiguation from the user (it's already unambiguous).

1 Like

I didn't though about it, but would that rule also apply to operators ?
Would it still be possible to write foo.reduce(0, +)

They don't really seem to suffer from the same ambiguity problem, since you can't use operator symbols as a variables.

1 Like

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 @brentdax.

I very much like this proposal.

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

Terms of Service

Privacy Policy

Cookie Policy