Overview:

In this tutorial, we are going to take a look at the Kotlin Standard Functions. The 6 most popular functions are: apply, let, run, with, also, takeIf. Each of these functions takes a lambda as a parameter – an easy way to think of a lambda is to think of it as an anonymous function that affects the original object. How exactly the original object (technically referred to as the Receiver) is affected depends on the function. As there is a plethora of in-depth technical discussion on each of these functions, we will focus instead on demonstrating readable examples of each.

We’ve created a User class to help support some of the functionality in this tutorial, visit the Github repository to view the full source if you’re interested.

Recommended:

Source Code:
https://github.com/codetober/kotlin-standard-functions

Apply

The apply function takes a lambda and executes the contained logic on the Receiver (original object). A great use case for apply is to initialize the field values of an object, such as a file; however, in this tutorial, we will be using the User class.

 val canineUser = User()
 canineUser.name = "Carat"
 canineUser.occupation = "Guard Dog"
 canineUser.age = 4

 // Using Apply to configure
 val human = User().apply{
     name = "Doug"
     occupation = "Keeper"
     age = 26
 }

Notice that when we created canineUser we manually assigned each of the name, occupation, and age fields manually using dot-notation. Because apply scopes the lambda to our Receiver we can omit the Receiver.field portion of the field initialization; instead, we directly set the field values.

Let

Unlike applylet grants access to an instance of the Receiver, this allows us to mutate the object as much as we desire. A really simple of this is to manipulate a String. In the below example, we will be chaining two let definitions together with dot-notation; it’s important to understand that each let returns the new value, not the original – we will leverage this to make cumulatively build a String.

 val destination = "County".let{
     it.plus(" Dog Park! ")
 }.let(::enhanceString)

 println(destination)

 // Outside of "main"
 private fun enhanceString(original: String) = 
    original.plus(listOf("Amazing!", "I'm Ready!", "Love it!")
    .shuffled()
    .first())

We use the it keyword to access the instance of the original object Country (string). In the first lambda, we append the string “Dog Park!”, this value is returned from the lambda and pushed into the second lambda. The original object for the second lambda is now “County Dog Park!”; we call another function enhanceString using a shorthand notation – to further append a random String to the end of the original object.

Run

Run is VERY similar to apply; with the exception that it does not return the Receiver. Instead, run returns a boolean value based on the operation that is scoped to the Receiver. In the following snippet, we are checking if our canineUser is readyToPlay. This condition is only true when the name of the User is equal to “Turbo”.

 val isReadyToPlay = canineUser.name.run(::isPlayful)
 println("$isReadyToPlay - Ready to Play?")

 // Defined outside of "main"
 private fun isPlayful(canineName: String) = canineName == "Turbo"

With

With is functionally identical to run with one major difference. with requires that the original object is passed as a parameter. This feels really awkward to use because it is syntactically different than the rest of standard functions – so, we tend to avoid using it.

 val isReadyForRide = with(destination){
     contains("I'm Ready!")
 }
 println("$isReadyForRide - Ready for Ride?")

Also

Just like the let also passes the original object into the lambda, however, instead of returning it as the result of the lambdaalso returns the original object. We can chain these together as well; for our example, we will print the status of our canineUserhunger field and then manipulate the original object based on its state – when a dog is hungry, we feed them.

 canineUser.also{
     println(it.hungry.toString() + " - Initial Hunger")
 }.also{
     if(it.hungry){
         it.makeFull()
     }
 }
 println(canineUser.hungry.toString() + " - After Hunger Check")

TakeIf

The very last standard function covered in the tutorial is takeIf. This function is very useful for checking the state of an object before attempting an operation. TakeIf evaluates a predicate provided to the lambda, in the case that the predicate evaluates to true, the Receiver is returned. However, if the predicate evaluates to false, null is returned. Make sure to use the null-safe ?.syntax to call the trailing function. Using this syntax will ensure that it is only executed when the result of the lambda is not null.

In our example, we will check if the human User has the name “Doug”. If so, we call a User-scoped function to setReady() on the human. It’ll be a long walk to the “County Dog Park!” without a human to provide the r-i-d-e.

 val isHumanReadyToGo = human.takeIf{
     it.name == "Doug"
 }?.setReady()

 println(human.ready.toString() + " - Is Human Ready?!")

That’s it! Congratulations on making it all the way through this tutorial and continuing your quest to craft amazing code! If you liked this tutorial, please share it 🙂 Comment below with any questions or issues you faced.