Some arguments in favor of Kotlin named parameters

Brian Terczynski
3 min readOct 9, 2021

--

Photo by Marc Reichelt on Unsplash

Kotlin has this great feature where you can indicate the names of the parameters when you call a function. For example:

acct = getAccount(accountNumber = "1234", routingNumber = "5678")

What’s nice is that it makes it clear to the reader what each parameter is, without having to go look at the function definition to see which parameter is at each position. More importantly, it ensures that the correct values are passed in to the correct parameters. So if the author got the parameters in the wrong order, named parameters ensure the correct values still get assigned to the correct parameters:

acct = getAccount(routingNumber = "5678", accountNumber = "1234")

The above examples are not very realistic, in that you would not pass in “magic constants” like this; rather you would pass variables in to your functions. So assuming getAccount() was defined as follows:

fun getAccount(accountNumber: String, routingNumber: String): Account

And you got the ordering of the parameters wrong:

acct = getAccount(routeNum, acctNum)

This code would, of course, break in production because you are transposing the parameters. The nightmare case is that this function actually returns something, but it returns the wrong account because you passed the wrong values in!

If you use named parameters, it would be clear at the callsite that the correct values were being passed to the correct parameters:

acct = getAccount(routingNumber = routeNum, accountNumber = acctNum)

Where it gets weird is if the names of your actual parameters match the names of the formal parameters. The below looks a little redundant:

val accountNumber = viewBinding.accountField.text
val routingNumber = viewBinding.routingField.text
acct = getAccount(
routingNumber = routingNumber,
accountNumber = accountNumber
)

Despite how repetitive this looks, I would still argue this is advisable, because you are ensuring that the proper values are passed to the correct parameters. It is just circumstance that the names of the values match the names of the parameters, but by using named parameters you avoid accidental transposition.

Note that such transposition will get caught by the compiler if neighboring parameters have different types. For instance, if you have a function with a String followed by a Boolean parameter, then if you transpose those parameters the compiler will of course catch that:

fun getBank(routingNumber: String, isCreditUnion: Boolean): Bank// Callsite
bank = getBank(isCreditUnion, routeNum) // Generates compiler error

There are also cases where parameter transposition may not matter. For instance, this is true of symmetric functions. One example is addition, where you can transpose the parameters and the result is the same:

fun vectorAdd(vector1: Vector, vector2: Vector): Vectorresultant1 = vectorAdd(Vector(1, 2), Vector(3, 4))
resultant2 = vectorAdd(Vector(3, 4), Vector(1, 2))
assertEquals(resultant1, resultant2)

For the case above, using named parameters is really not necessary, and in fact can make the code look a bit clunkier:

// Named parameters in this case doesn't look as nice, and does
// not provide much benefit
resultant = vectorAdd(
vector1 = Vector(1, 2),
vector2 = Vector(3, 4)
)

But for non-symmetric functions, named parameters are helpful when neighboring parameters have the same type. Because, like in the bank account case above, the compiler will not catch such coding errors. Even unit testing may not catch such issues if you are simply using mock answers for functions (especially if the parameter matchers are “any”). As such, I would recommend making use of named parameters in non-symmetric functions, even in cases where the callsite parameter names match the declared parameter names.

--

--

Brian Terczynski
Brian Terczynski

Written by Brian Terczynski

Documenting my learnings on my journey as a software engineer.

No responses yet