Functional kotlin part 3: scoping functions

In the part 3 of the series of the posts about kotlin we going to look into the one 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 a classic Java Util classes with method twice(int) but done in a very clean way. Visually it looks like you're calling a member of the class, but in reality, the compiler calls your function passing receiver as an argument.

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 the .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 behaviour - they both change the context of the function but unlike .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