Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusion with understanding lambda and receivers

Kotlin version 1.2.50

I have been following some examples on this tutorial on youtube https://www.youtube.com/watch?v=gPH9XnvpoXE. And there are a few things that I have understood, but there is still some confusion. I have left the comments in the code below where I am not sure what is happening.

fun main(args: Array<String>) {

    val javaClient = createClient {
        firstName = "joe"
        lastName = "bloggs"

        twitter {
            handle = "@joebloggs"
        }
    }

    println(javaClient.toConsole)
}

/* Are we passing in a lambda and receiver. What will the receiver be */
private fun JavaClientBuilder.twitter(suppler: JavaTwitterBuilder.() -> Unit) {
    /* We call JavaTwitterBuilder().apply(..) Will apply return the newly created object? Not sure why we have to pass the suppler in the apply */
    twitter = JavaTwitterBuilder().apply(suppler).build()
}

/* Are we passing in a lambda and receiver that return nothing */
private fun createClient(suppler: JavaClientBuilder.() -> Unit): JavaClient {
    val javaClientBuilder = JavaClientBuilder()

    /* confusion: Not sure about this, as we are calling suppler. Just wondering is the suppler the the JavaClientBuilder that was called in the above javaClient {} lambda */
    javaClientBuilder.suppler()

    return javaClientBuilder.build()
}

/* I understand this, an extension function that formats and returns the string from the JavaClient object it was called on */
private val JavaClient.toConsole: String
    get() =
        "Created client is: ${twitter.handle} ${company.name}"
like image 462
ant2009 Avatar asked Oct 07 '18 12:10

ant2009


2 Answers

/* Are we passing in a lambda and receiver. What will the receiver be */

private fun JavaClientBuilder.twitter(suppler: JavaTwitterBuilder.() -> Unit)

We've indeed got a receiver in this function, and it's the instance of JavaClientBuilder that this function will be invoked on.

/* We call JavaTwitterBuilder().apply(..) Will apply return the newly created object? Not sure why we have to pass the suppler in the apply */

twitter = JavaTwitterBuilder().apply(suppler).build()

To understand how apply() works, take a look at its source code (simplified version):

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

It's an extension function declared on a receiver of type T and returning an instance of T, which takes in a block - another extension function with receiver of type T returning Unit. It's usually used to replace the Builder pattern - apply custom initialization logic to an object. In your case, suppler is a block that contains initialization logic for an instance of JavaTwitterBuilder. The function code creates the instance and uses apply() with the logic in suppler to initialize that instance.

/* Are we passing in a lambda and receiver that return nothing */

private fun createClient(suppler: JavaClientBuilder.() -> Unit): JavaClient

In this case, createClient() doesn't have a receiver, it's a top-level function.

/* confusion: Not sure about this, as we are calling suppler. Just wondering is the suppler the the JavaClientBuilder that was called in the above javaClient {} lambda */

javaClientBuilder.suppler()

suppler is a lambda with JavaClientBuilder being the receiver type, which allows us to call it on the newly-created instance of JavaClientBuilder.

/* I understand this, an extension function that formats and returns the string from the JavaClient object it was called on */

private val JavaClient.toConsole: String get() = "Created client is: ${twitter.handle} ${company.name}"

Right! Just a small correction, it's an extension property. Properties can have custom getters and setters. This property defines a custom getter, so indeed whenever this property is accessed it will produce a String with the format described by the getter code.

like image 73
Egor Avatar answered Sep 28 '22 11:09

Egor


Hope the following example helps understand lambda with receiver type:

data class Person(val name: String)

fun getPrefixSafely(
    prefixLength: Int,
    person: Person?,
    getPrefix: Person.(Int) -> String): String
{
    if (person?.name?.length ?: 0 < prefixLength) return ""
    return person?.getPrefix(prefixLength).orEmpty()
}

// Here is how getPrefixSafely can be called
getPrefixSafely(
    prefixLength = 2,
    person = Person("name"),
    getPrefix = { x -> this.name.take(x) }
)

PS: These functional literals with receiver types are similar to extension functions IMO.

like image 26
Amit Dash Avatar answered Sep 28 '22 09:09

Amit Dash