19-Dec

Kotlin

Streamlining your functions with Named and Default Arguments

As developers, we spend a considerable amount of time declaring and invoking functions when writing software. Kotlin provides several features that can boost your productivity when working with functions. In this post we'll take a closer look at two of these features; Named Arguments and Default Arguments, and how we might use them to make our Christmas preparations more carefree!

4 min read

·

By Sondre Larsen Ovrid

·

December 19, 2019

Named Arguments

When declaring functions we may specify an arbitrary number of parameters that the function expects in order to perform its logic. For example we may declare the following function for preparing Christmas gifts:

fun prepareChristmasGift(
    nameTag: String,
    applyWrapping: Boolean,
    applyTape: Boolean,
    applyRibbon: Boolean,
    applyGiftBow: Boolean
) { ... }

Likewise the caller of the function would probably call the function with the corresponding arguments like so:

prepareChristmasGift("John Doe", true, true, true, false)

Now first of all, you may have noticed that the order of the arguments given to the function upon calling it, is not arbitrary. As you might already know, we have to adhere to the order of the parameters as given in the function signature, since arguments will be mapped to their corresponding parameter counterpart by position.

Named Arguments, on the other hand, lets us specify explicitly which particular parameter we are mapping the argument value to. Utilising this concept, we may instead call the function the following way:

prepareChristmasGift(
    nameTag = "John Doe",
    applyWrapping = true,
    applyTape = true,
    applyRibbon = true,
    applyGiftBow = false
)

Alternatively, we may omit the name of one or more of the arguments:

prepareChristmasGift(
    "John Doe",
    applyWrapping = true,
    applyTape = true,
    applyRibbon = true,
    applyGiftBow = false
)

Keep in mind though, if we're mixing named and un-named (positioned) arguments, only trailing arguments can be named. E.g. it would be possible to omit the name for the applyWrapping argument but not for applyGiftBow, in the example above.

Although a little more verbose, using Named Arguments arguably makes it easier to understand what the function does, without having to look at the function signature itself, or using code inspection if your IDE supports it. This becomes particularly evident whenever a function, like ours, expects boolean literals as arguments. Some IDE's actually encourage this convention, as can be seen in IntelliJ's tooltip:

"This inspection reports boolean literal arguments that could be named to remove ambiguity."

Additionally, as we'll see later, we can make function calls even more flexible and sometimes less verbose when combining Named and Default Arguments.

A word about Java Interop

In the example above we made the assumption that our function was both declared and called from within Kotlin code. If we're making use of Kotlin's interop with Java, we might be tempted to try and call Java methods with named arguments from within Kotlin code. By default this is not supported, and thus trying to pass arguments to a Java method call by specifying its parameter name, would result in a compiler error.

Rewriting the Java method to a Kotlin function might not always be an option. In such cases there are some alternatives to named arguments in the context of Java interop; such as utilising the Builder Pattern, that might give similar possibilities, although requiring more overhead.

Default Arguments

There are use cases when we might want to enable the user of our functions to omit certain arguments, if possible. If you're coming from a language like Java, you may be familiar with the concept of function overloading. The following example is a frequently used pattern to achieve omissible function arguments by utilising function overloading:

fun overloaded(a: Boolean, b: Boolean, c: Boolean, d: Boolean) { ... }

fun overloaded(a: Boolean, b: Boolean, c: Boolean) =
    overloaded(a, b, c, false)

fun overloaded(a: Boolean, b: Boolean) =
    overloaded(a, b, false)

fun overloaded(a: Boolean) =
    overloaded(a, false)

In practice we have now allowed the caller of the function to omit arguments for b, c, d, which will be supplied correspondingly with false as the default value.

With Default Arguments though, we can achieve the same functionality with a fraction of the overhead. Suppose we wanted to modify our gift-wrapping function with a default value for the applyGiftWrapping parameter. After all; half the fun with receiving Christmas presents lies in the unwrapping, so we can expect that most of the callers will want to have this flag set to true!

Default Arguments are specified in the function declaration using = followed by the default value. Applying this to our function, we get the following:

fun prepareChristmasGift(
    nameTag: String,
    applyWrapping: Boolean = true,
    applyTape: Boolean,
    applyRibbon: Boolean,
    applyGiftBow: Boolean
) { ... }

We may take this a step further, supplying default values for the majority of our remaining parameters:

fun prepareChristmasGift(
    nameTag: String,
    applyWrapping: Boolean = true,
    applyTape: Boolean = true,
    applyRibbon: Boolean = false,
    applyGiftBow: Boolean = true
) { ... }

Now it is possible to call the function while omitting some arguments, which instead will be supplied behind the scenes with the default values that we specified:

prepareChristmasGift("John Doe")

Neat! Keep in mind that, unless we combine this with Named Arguments, only trailing arguments can be omitted. If we wanted to supply the applyRibbon and the applyGiftBow with arguments other than the default ones, we would also have to give values for preceding arguments.

Combining Named and Default Arguments

​If you're anything like me, you have a ton of presents to prepare for Christmas, but only so much time. No worries! Let's speed up the process a bit by combining Named and Default Arguments. Personally I prefer ribbons over gift bows, so that's what I'll stick to when calling my Kotlin functions as well:

val giftBag = listOf(
    prepareChristmasGift("John Doe", applyRibbon = true, applyGiftBow = false),
    prepareChristmasGift("Jane Doe", applyRibbon = true, applyGiftBow = false)
    ...
)

Summary

​Named and Default Arguments lets us write more user friendly and generalized functions, with little overhead. In return we get more readable code, and often a quicker development process.

Up next...

Loading…