Allow omitting argument values when variable present with same name as label

Frequently in my code I calls like this where there are in-scope variables named color, size, and temperature and I cannot change someMethod to take positional arguments:

someMethod(color: color, size: size, temperature: temperature)

I would love to eliminate the repetition, perhaps like this:

someMethod(color:, size:, temperature:)

If it is problematic for colors to appear here, maybe another character could be used like:

someMethod(color*, size*, temperature*)

1 Like

This seems like an interesting idea, but have you considered using underscores before the function identifiers to eliminate the need for any naming when passing in the arguments to the function? ex:

func someFunction(_ color: Color, _ size: CGSize, _ temperature: Double) {}

// Call the function without passing argument names
// Assume "color", "size", and "temperature" are already defined
func someFunction(color, size, temperature)

Those characters (the star and the colon) already have uses though, and shouldn't be re-used in such a context either.

The thing is, in most argument lists it's poor Swift style to give a parameter a label that would also be a good variable name. For instance, your function’s color, size, and temperature labels probably duplicate information available from the types of those parameters, which means they’re redundant and you shouldn’t use them. A more plausible example might look like:

someFunction(with: color, jelloCubeOf: size, at: temperature)

But with, jelloCubeOf, and at are not plausible names for a variable of those types, so you wouldn’t be able to use that feature here. And that isn’t a contrived example; the API Design Guidelines call for you to "prefer method and function names that make use sites form grammatical English phrases”, and arguments that are prefixed by nouns are not usually going to form grammatical English phrases.

There are a couple of cases where it’s idiomatic to use argument labels that might work as variable names—optional arguments, arguments to initializers and factory methods, etc.—but these aren’t super-common, and I’m not convinced it’s worth adding a feature for them.

9 Likes

Yes, I know I can use understands in the function definitions to make the arguments positional, but typically I'm calling library functions that use named arguments.

Although I don't think the OP's idea is worth pursuing, because, although this sort of redundancy is common, it's not so common as to warrant a new feature, I also don't think the original intention of the following actually ended up looking as readable as intended:

Main reason: .init. But literals cause readability problems too.

someFunction(with: .init(gray: 0.5), jelloCubeOf: [1, 6], at: 33.2)

I never thought the bare prepositions were a good idea, and six years after the biggest change, I'm still not convinced. The separation of argument labels and parameter names is good, but primarily for removing prepositions from parameters, when the argument labels already include the direct objects.

4 Likes

Initializers are probably the most common place I run into this duplication. For example:

let person = Person(firstName: firstName, lastName: lastName, birthday: birthday)
2 Likes

That sounds like a use case for the ever-missing feature of applying functions, Optional.map-style, to everything. E.g.

let person = (firstName, lastName, birthday)…Person.init
infix operator …

///- Remark: Hold option, press ;
@discardableResult
public func … <Input, Transformed>(
  input: Input,
  transform: (Input) -> Transformed
) -> Transformed {
  transform(input)
}
2 Likes

Possible solution would be to have a third case (optional parameter names) in addition to the two we already have: empty/external argument name (required parameter name) and "_" (no parameter name):

// not in Swift, straw-man syntax:
func someMethod(color _: Color, size _: Size, temperature _: Temperature) {}
// or this:
func someMethod(__ color: Color, __ size: Size, __ temperature: Temperature) {}
// or this:
func someMethod(~ color: Color, ~ size: Size, ~ temperature: Temperature) {}

// usage - can use either with or without parameter name:
someMethod(color: color, size: size, temperature: temperature) // ok
someMethod(color, size, temperature) // ok
someMethod(color: color, size, temperature) // still ok

This is a case where the duplication is helpful, because if firstName and lastName are the same type (probably String), it would be easy to mix them up without the labels, and with the labels, a glance is enough to notice if they're backward.

Your original example (where all the arguments are different types) is a case where I'd consider omitting the labels, because there's no possibility of mixing up the argument order. Or I might use a preposition to label the first argument and omit the labels of the rest.

3 Likes

What if the underscore were used in a reversed sense? For example, at the call site:

someMethod(color: _, size: _, temperature: _)

and the compiler will substitute the underscore with whatever variable has the same name as the argument name. I think the refactoring tool would need extra logic to recognise this if you tried to rename a variable using it.

There’s no issue with types because what is specified is a list of labels, not variables. It is saying “I have variables whose names match the labels, so use those as the values.”

This makes sense to me but it should be a feature of the editor not the compiler.

2 Likes

i understand the motivation of that guideline, but in practice trying to make API calls read like english sentences ends up making really brittle APIs that change frequently, are hard to refactor, and run counter to what would be the most logical organization from an implementation standpoint.

programming complex systems is hard enough without trying to be a poet at the same time, 75% of the time the best argument label is what the name of the variable would be in the calling scope.

of the common prepositions, as: and to: are probably the only useful argument labels. i tend to have a hard time choosing between of: and for:, and i can’t remember many times where by: contributed any clarity at all.

6 Likes

But it's obvious that data(for:delegate:) takes URL and data(from:delegate:) takes URLRequest... NOT!

(and it's the other way around)

Besides it's still not "English". "data(for: x, delegating: y)" would be...

There's no universal consistency. Sometimes "forKey" is used even when there's no other "forXxx", other times function is overloaded and it's "from:" argument accepts different types. Then there are some serious æsthetic offenders: CGAffineTransform(scaleX:y:)

I'd echo the previous two posts admitting that while this is not "swifty", oftentimes I'd personally prefer seeing some less grammatically correct form... e.g. something as simple as struct's default member-wise initialiser rules that results into Foo(bar:baz:) for a struct with bar and baz fields.

1 Like

I think it’s worth mentioning that Scala has very similar feature called implicit parameters. It might be interesting to see how it’s going there.

https://docs.scala-lang.org/tour/implicit-parameters.html

I vote no on this on this for the simple reason that swift should remain as exception free as possible. There is very little gain here.

2 Likes