In 3 of the series of the posts about kotlin we going to look into of the intensively used kotlin extension functions from the standard library - they allow to write very expressive and safe, functionally-looking code.
For folks who got lost on the word "extension functions" - it's a way to attach a function or property to the instances of the existing classes. For example, val d = 10.twice()
It's very much like
T.let
let
allows passing given reciever into the parameter of the lambda, returns result from it. Essentially it's an map
operator on the single value
val imageData = db.getUserById(userId)
?.let { it.getProfilePicture().url }
?.let { fetchData(it) }
How to use?
It's very useful when it comes to the conditional on nullable object, think of functional replacement for if (user != null) user.ban()
:
user?.let { it.ban() }
// or even
user?.let(::ban)
Often we use expressions in the string interpolation, .let
can be very handy:
println("Hello ${db.getUser(id)?.let { it.name } ?: "NONAME"}")
T.apply
apply
calls given lambda in the context of receiver, similar to the with
found in JavaScript and Groovy.
User().apply {
name = "user name"
password = "qwerty"
}
How to use?
I found it's a way to patch encapsulate configuration/finish object initialisation. It helps to perform number of operations within lambda block which are logically related to the operation such as factory initialisation or object creation. apply
returns the receiver object(object it was called on)
So instead of
val awsS3Client = AwsS3Client()
val credentials = AwsCredentialsFactory("key", "secret")
credentials.setForceHttps(true)
awsS3Client.credentials = credentials
awsS3Client.bucket = "my_bucket"
We can have nicely organized initialization block:
val awsS3Client = AwsS3Client().apply {
credentials = AwsCredentialsFactory("key", "secret").apply {
setForceHttps(true)
}
bucket = "my_bucket"
}
Nice and clean, without messy noise and scope pollution!
T.also
It behaves exactly as .let
with one exception - with return result. .also
takes receiver and passes it as an argument for the given lambda, returning the same receiver
val bannedUser = db.getUserById(userId)
.also { println("Got user $it") }
.ban()
How to use?
From my experience it can be used for debugging - as demonstrated above. Also, it can be used a replacement for .apply
one can say it's more clear - based on the example for .apply
:
val awsS3Client = AwsS3Client().also {
it.credentials = AwsCredentialsFactory("key", "secret").apply {
it.setForceHttps(true)
}
it.bucket = "my_bucket"
}
T.run
/ with()
These have very similar ehaviour - they both change the context of the function but apply
the result of the function would be something lambda returned
val userId = User().run {
name = "username"
password = "foobar"
db.save(this).id
}
val userId = with(User()) {
name = "username"
password = "foobar"
db.save(this).id
}
These two have identical behaviour - in both cases object is initialised, saved and generated id is returned - the result of the last line expression
How to use?
Well, I didn't find it used often in my code, but the example above may give you some idea
Combo
Some examples from the real projects
// filter out images with faces, print debug info, annotate
// image with labels and filter images by stop words
val images = allImages
// filter pics with faces if necessary
?.let { if (allowFaces) it else withoutFaces(word, it) }
?.also { log.info("findImageForWord: got ${it.size} pics after face filtering") }
// image labelling
?.let { findLabels(word, it) }
// exclude stop list words
?.filterNot { it.normalizedLabels.any { it in stopList } }
// Prepare video upload request for the YouTube API:
val video = Video().also {
it.snippet = VideoSnippet().also {
it.set("categoryId", "27")
it.set("description", description)
it.set("title", title)
it.set("tags", tags)
}
it.status = status
}
I hope you found this article useful for you. Check out other posts about functional constructions to get the most out of kotlin