¿Qué significa T.() -> Unit?

Hola! En este artículo vamos a desglosar que significa este tipo de nomenclatura cuando lo vemos en las funciones de kotlin. De seguro que lo habrás visto de esta manera:

fun <T> functionWithReceiver(block: T.() -> Unit){

}

Aquí lo que te está pidiendo la función functionWithReceiver es que le pasemos como parámetro otra función. Aquí functionWithReceiver tiene que llamar a block pero en el contexto de un receptor de tipo T.

Veamos un ejemplo práctico:

fun functionWithInt(block: Int.() -> Unit){

     5.block()

}

En este ejemplo la función functionWithInt pide como parámetro una función la cual se va a encargar de llamar en el contexto de un receptor de tipo entero. Prácticamente está diciendo dame una función y yo me encargo de pasarte el receptor de tipo Int que en este caso vendría a ser el numero 5.

Utilizando la función anterior haríamos esto:

fun main() {
    functionWithInt {
        val result = this * 10  
                          // 5    * 10 
        println(result)   // 50
    }
}

fun functionWithInt(block: Int.() -> Unit): Unit{
    5.block()
}

Entonces:

Dentro de la función main utilizamos la función declarada functionWithInt a la cual le tenemos que pasar un lambda. Nosotros al crear el lambda tenemos que considerar que este lambda está en función de un receptor al cual nos podemos referir con la palabra this, receptor que dicho sea de paso aún no conocemos ya que quien lo asigna es la declaracion de functionWithInt con un valor de 5 en este caso.

Entonces el this aquí es el número 5 multiplicado por 10 = 50, valor que finalmente es impreso.

Ahora vamos a complicar un poco las cosas utilizando una conocida scope function de kotlin llamada apply. Si nos dirigimos a la declaración de la función encontramos esto:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

Encontramos una función genérica de tipo T el cual funciona como receptor del apply, lo que quiere decir que dentro de la función apply nos podemos referir al elemento T con la palabra this.

Vamos a obviar el lambda contract y nos vamos a dirigir a la llamada de la función que recibimos como parametro de la función apply, es decir block.

Aquí algo que no se explica es que el this en estos casos tambien puede ser obviado dentro de la función apply, es decir la función utilizando explicitamente todo seria:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    this.block()    <---- Aquí 
    return this
}

Entonces lo que hace la función apply es llamar al lambda que recibió como parámetro sobre el receptor que está utilizando el apply.

Veamos una aplicación práctica:

fun main() {
    val animal = Animal().apply {
        name = "Garfield"
        color = "Orange"
    }
    println(animal)  // Animal(name=Garfield, color=Orange)
}

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    this.block()    <---- Aquí 
    return this
}

data class Animal(var name: String = "", var color: String = "")

En este caso dentro de nuestra función main en vez de nuestro genérico T estamos utilizando un objeto de la clase Animal y el cual vendría a ser el receptor. Le pasamos un lambda en el cual la función apply se encarga de llamar sobre el receptor Animal, es decir que en esta parte:

public inline fun <Animal> Animal.apply(block: T.() -> Unit): T {
    this.block()    <---- Aquí el this es el objeto animal y sobre el objeto se llama al lambda
    return this
}

El lambda agrega estas propiedades:

{
    this.name = "Garfield"
    this.color = "Orange"
 }

El this hace referencia a el objeto animal y le agrega dos propiedades name y color. Tambien podríamos haberlo escrito así:

{
    name = "Garfield"
    color = "Orange"
 }

Finalmente la función apply retorna el this que vendría a ser el objeto con con las propiedades name y color agregadas, dandonos la impresión:

Animal(name=Garfield, color=Orange)