Fancier Functions

Those basics are enough to allow you to write Kotlin code. However, there are a few additional features that are fairly prevalent in Kotlin programming. How much you use these features is up to you, but you will encounter them frequently and so it is important to understand how they work.

Expression Bodies

Frequently, our function bodies are lines of code wrapped in braces, as shown above.

Sometimes, you will encounter functions that are declared using an = instead:

fun main() {
  println(expressionBody(1, 1))
}

fun expressionBody(left: Int, right: Int) = left + right

This is called “expression body” syntax.

It is designed to simplify cases where the entire method implementation is a single expression, such as adding two numbers together. Here, we replace a pair of braces and a return keyword with an =, which makes this code less verbose.

Expression Bodies and Types

Another thing that we eliminated in the above example is the return type. We did not need to declare that expressionBody() returns an Int, as the compiler can determine that on its own. It knows what the type of left is, and it knows what the type of right is. It even knows what the type of left + right is. So, it knows the type of expressionBody() as a result.

However, sometimes, you will need to override that type. For example, there will be cases where the expression evaluates to one type, but you want the function to return a supertype.

In those cases, you just add back the : syntax for declaring the return type:

fun main() {
  println(expressionBody(1, 1))
}

fun expressionBody(left: Int, right: Int) : Number = left + right

Here, we declare expressionBody() to return a Number. As it turns out, Int inherits from Number.

This particular example is silly. However, once we start getting into classes and inheritance, this capability becomes more important.

Why Bother?

In the end, this may not seem like a “big win”, for two reasons:

  1. We are only saving a few characters
  2. Not that many functions would appear to be simple enough that their entire implementation can be a single expression

The first argument is true: we are not saving much on a per-function basis. However, these simplifications add up over time. A lot of focus in Kotlin is on offering these small simplifications, in a lot of places, which combine to make Kotlin code much more concise than the equivalent code in Java and other languages.

In terms of the second argument… it turns out that a lot more things in Kotlin can be written in terms of expressions than you might think. We will see more about that in an upcoming chapter.

Default Parameter Values

Some languages offer default parameter values, such as JavaScript:

function increment(base, amount = 1) {
  // something yummy
}

…and Ruby:

def increment(base, amount = 1)
  # something yummy
end

Kotlin also offers default parameter values:

fun main() {
  println(increment(1))
}

fun increment(base: Int, amount: Int = 1) = base + amount

Here, we can call increment() with either one or two values. If we only supply one value, the default value of 1 will be used for amount. If we supply two values, then the caller controls both base and amount.

The equivalent Java code requires two methods:

int increment(int base, int amount) {
  return base + amount;
}

int increment(int base) {
  return increment(base, 1);
}

Named Parameters

Functions with lots of default values are often called using named parameters. In this form of a call, rather than identifying parameters by the order in which they appear in the call, you explicitly state the name of the parameter in the call itself. You can even “mix and match” positional parameters and named parameters:

fun main() {
  println(increment(base = 1))
  println(increment(amount = 10, base = 1))
  println(increment(1, amount = 10))
}

fun increment(base: Int, amount: Int = 1) = base + amount

Here, in the first two println() calls, we specifically state both the name and the value of the parameters in the function call. In the third println() call, the first parameter is a traditional “positional” parameter, while the other one is named.

In this particular example, we are not gaining much from this syntax. However, complex functions with lots of default parameter values can gain a lot more from this. Suppose that we had a function that looked like this:

fun iCanHazCookie(name: String,
                  value: String,
                  maxAge: Long? = null,
                  expires: LocalDateTime? = null,
                  domain: String? = null,
                  path: String? = null,
                  secure: Boolean = false,
                  httpOnly: Boolean = false) : String {
  // code to generate an HTTP Set-cookie header goes here
}

(as a reminder, types with a ? indicate parameters that support null values — we will explore this more later in the book)

Here, six of the parameters have default values defined, and so they are optional when we call iCanHazCookie().

Suppose that I want to create a cookie with a particular path, but I am willing to accept the defaults for the remaining five optional parameters. If we were stuck with positional parameters, we would have to call it like this:

val cookieHeader = iCanHazCookie("foo", "bar", null, null, null, "/")

In effect, the first three optional parameters are no longer optional, because we need them as placeholders.

With named parameters, this becomes simpler:

val cookieHeader = iCanHazCookie(name = "foo", value = "bar", path = "/")

…or even:

val cookieHeader = iCanHazCookie("foo", "bar", path = "/")

We can more easily skip over the parameters that we wish to ignore and accept the default values, because we can name the parameters that we are supplying, rather than relying on positions to identify them.

Traditionally, named parameters had to appear after all positional parameters. Kotlin 1.4 relaxed that restriction somewhat. You can use names for any parameters, but up through the last positional parameter, the positions (not the names) are what matter.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.