Intention of Kotlin's "also apply let run with"

Intention of Kotlin's "also apply let run with"

One of the things that puzzled me when I started with Kotlin was why are there so many similar functions which call lambda on some objects and returns a value. After many lines of code and many lines of user group discussions, I found out that they represent a small DSL for easier with classes. Explanation of this DSL and intent of each function is missing from the Kotlin documentation, so this article will hopefully shed some light on it. There is also a short style guide on GitHub.

also

With this function, you say “also do this with the object”. I often use it to add debugging to the call chains or to do some additional processing:

kittenRest
    .let { KittenEntity(it.name, it.cuteness) }
    .also { println(it.name) }
    .also { kittenCollection += it }
    .let { it.id }

also passes object as parameter and returns the same object (not the result of the lambda!):

val text = "How is it going?"
text.also{s -> s.toUpperCase()}

How is it going?

or shorter, with the default it parameter:

text.also{it.toUpperCase()}

let

let is a non-monadic version of map: it accepts object as parameter and returns result of the lambda. Super-useful for conversions:

val text="How is it going?"
text.let{it.toUpperCase()}

HOW IS IT GOING?

apply

apply is used for post-construction configuration. It is a function literal with receiver: object is not passed as a parameter, but rather as this. An object passed in such way is called receiver.

val parser = ParserFactory.getInstance().apply{
    setIndent(true)
    setLocale(Locale("hr", "HR"))
}

Here is simple text example:

val text="How is it going?"
text.apply{this.toUpperCase()}

How is it going?

or shorter:

text.apply{toUpperCase()}

run

run is used with lambdas that do not return value, but rather just create some side-effects:

text?.run{
    println(text)
}

It returns value of the expression, but it shouldn’t be used.

val text="How is it going?"
text.run{toUpperCase()}

HOW IS IT GOING?

with

According to Kotlin idioms, with should be used to call multiple methods on an object.

with(label) {
    setWidth(100)
    setHeight(50)
}

with should configure object, which was constructed somewhere else. In this example, it was GUI object probably constructed by some framework. In that respect, it is similar to apply. It returns the result of the last expression, which is confusing; you should ignore it. Note that it does not work with nullable variables.

val text="How is it going?"
with(text) {
    toUpperCase()
}
HOW IS IT GOING?

What to Use When

Here is a short overview of what each function accepts and returns:

Parameter Same Different
it also let
this apply run, with

I was not particularly happy with the decision of standard library designers to put so many similar functions, as they represent cognitive overload when analyzing the code. However, if you strictly use them for their intended purpose, they will state your intent and make the code more readable:

  • also: additional processing on an object in a call chain
  • apply: post-construction configuration
  • let: conversion of value
  • run: execute lambda with side-effects and no result
  • with: configure object created somewhere else

Do not use with on nullable variables and avoid nesting apply, run and with as you will not know what is current this. For nested also and let, use named parameter instead of it for the same reason.

All these functions can be replaced with let, but then information about the intent is lost. As intent is the most valuable part of this set of functions, be careful when to use which one.

Comments

Popular Posts

Kotlin and Java EE: Part One - From Java to Kotlin

Kotlin and Java EE: Part Two - Having Fun with Plugins

Kotlin and Java EE Part Three - Making it Idiomatic