Learn: Kotlin Standard Functions – apply, let, run, with, also, takeIf
Learn: Kotlin Standard Functions – apply, let, run, with, also, takeIf
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 apply, let 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 canineUser
hunger 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.