Proposal self named arguments: Required named arguments unless variable name matches name of argument

So, I have this idea for a long time and want to share it with you. What if Scala somewhere in the future will always require use of named arguments, unless passed variables have matching names with the names of arguments? Let me show you the example:

def foo(processId: Int, title: String, isHighPriority: Boolean) = ???

And we have the following variables:

val processId = 1
val anotherProcessId = 2
val title = "main"
val isHighPriority = true

In that cases compiler will accept the follwing

foo(processId, title, isHighPriority) // obviously

foo(isHighPriority, processId, title) // still ok, because compiler doesn't care about order, he matches the names
foo(isHighPriority = false, title, processId = 1) // as a result, we can freely mix and use named and not named variables in any order

And here are some restrictions:

foo(processId = anotherProcessId, title, isHighPriority) // even thou we are passing variables in the right order, one of them has different name than its argument
foo(processId, title: "main", isHighPriority) // we are passing the value directly, so we need to explicitly tell the name of the argument
foo(processId, title, isHighPriority = !isHighPriority) // name of the last variable matches, but we are not passing it, but rather result of operations on that variable, so we need to name it

What are the benefits? I can name a few:

  • Better readability. Without checking the definition of the function, we can tell from code what is being passed and to what arguments.
  • More freedom. The definition and usage are almost completely decoupled.
  • Safety. Just imagine that someone decided to move around arguments.
def foo(childId: Int, parentId: Int) // old definition
def foo(parentId: Int, childId: Int) // new definition
...
// Finding this bug in the code can me a nightmare: 
foo(childId, parentId) // somewhere the function is being called the 'old way'
  • If you think that this is not going to happen with responsible programmers, proper code review, and etc, then unfortunately you are too naive. This happens more often than it should be in the sane world. Especially when the library or code is not mature enough and still evolving.
  • We will softly push programmers to write cleaner code and make more meaningful names for arguments. Like soft opionated view on how to write clean code.

So, what do you think?

this isn’t backwards compatible in the source - so there would need a transition period

Then you can’t call Java methods anymore because those can’t be used with named arguments. And every time you call an object of type FunctionN you need to add meaningless parameter names like v1, v2 etc. It also means that renaming any method parameter could lead to many local variables in completely unrelated functions having to be renamed.And it breaks every existing function call where two parameters are intentionally swapped.

What do you get in return? Being able to pass the arguments in an arbitrary order based on name matching is cute. It’s also entirely useless: just put them in the right order. That’s easier to read too because it’s more consistent.

The claimed readability benefits can be achieved with better tooling. It shouldn’t be hard to add a feature in Metals to display parameter name inlays. And if you really want to, you can enforce named parameters with a Scalafix rule. They’re not even that hard to write, actually.

You said “always require use of named arguments” – whatever that is, it’s not “softly push” or “soft opinionated”.

Thanks but no thanks.

1 Like

I disagree, I’m afraid. The semantics of the call site are often different from those of the function, especially if the function is well-encapsulated and general, and the names often should be different.

I can think of more cases where this would be a net negative than a positive, and most of the time, the better solution is stronger types.

There are times where this would avoid bugs, yes – but I believe it would actually tend to lead to less-comprehensible code, as people wind up using the function parameter names at the call sites regardless of whether those names are appropriate in the broader context or not.

2 Likes

This is true, but also solvable. For example, Swift allows you to define separate internal and external names for function params Documentation

func greet(person: String, from hometown: String) -> String {
    return "Hello \(person)!  Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill!  Glad you could visit from Cupertino."

Sure, but that kind of misses my point: the function author’s expectations of naming often just doesn’t match what makes sense at the call site. So you wind up with the boilerplate of having to name your arguments in the call, even when they’re completely unambiguous. Doesn’t seem like a win to me.

(Mind, I think this is pretty reasonable in less-strongly-typed languages – the tradeoffs are very different. But I would pretty passionately hate this in Scala.)

3 Likes

I agree.

I’ve contributed some linting around named args, and a bothersome case is that people use variable names as documentation:

f(x, y, z) // for def f(x: Int, y: Int, z: Int)

which of course is visually appealing but offers no safety.

Instead of requiring disambiguating redundant names

f(x = x, y = y, z = z)

it is more natural to simply lint the correspondence of var name and param

f(z, y, x) // did you mean f(x, y, z)?

A common pattern for naming is

f(start, end)

so that is never confusing, either because the names correspond to params, or on some other basis (start is to the left of end, f(start, point, end)).

I would propose more generous or forgiving language than “you are too naive” or these things shouldn’t happen in a “sane world”. That is, we can accept that these things happen in a creative profession as practiced by knowledgeable, well-intentioned people. But I like “soft” opinion: like a “soft” keyword, it offers contextual safety.

A soft opinion is not less opinionated, but it promises not to annoy with stridency.