descargar - ¿Qué es un "receptor" en Kotlin?
kotlin vs scala (4)
Literales de funciones / Lambda con receptor
Kotlin apoya el concepto de "literales de función con receptores". Permite el acceso a métodos y propiedades visibles de un receptor de una lambda en su cuerpo sin ningún calificador adicional . Esto es muy similar a las funciones de extensión en las que también es posible acceder a miembros visibles del objeto receptor dentro de la extensión.
Se
apply
un ejemplo simple, también una de las mejores funciones de la biblioteca estándar de Kotlin:
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Como puede ver, tal función literal con receptor se toma como el
block
argumento aquí.
Este bloque simplemente se ejecuta y se devuelve el receptor (que es una instancia de
T
).
En acción, esto se ve de la siguiente manera:
val foo: Bar = Bar().apply {
color = RED
text = "Foo"
}
Instanciamos un objeto de
Bar
y llamamos a
apply
.
La instancia de
Bar
convierte en el "receptor".
El
block
, pasado como argumento en
{}
(expresión lambda) no necesita usar calificadores adicionales para acceder y modificar el
color
y el
text
propiedades visibles mostradas.
El concepto de lambdas con receptor también es la característica más importante para escribir DSL con Kotlin.
¿Cómo se relaciona con las funciones de extensión?
¿Por qué es
with
una función
, no una palabra clave?
Parece que no hay documentación explícita para este tema, solo la suposición de conocimiento en referencia a las extensions .
En pocas palabras (sin palabras ni complicaciones adicionales), el "Receptor" es el tipo que se extiende en la función de extensión o el nombre de la clase. Usando los ejemplos dados en las respuestas anteriores
fun Foo.functionInFoo(): Unit = TODO()
Tipo "Foo" es el "receptor"
var greet: String.() -> Unit = { println("Hello $this") }
Tipo "Cadena" es el "Receptor"
Consejo adicional: Esté atento a la clase antes de la parada completa (.) En la declaración de diversión
fun receiver_class.function_name() {
//...
}
Es cierto que parece haber poca documentación existente para el concepto de receptores (solo una pequeña nota al margen relacionada con las funciones de extensión ), lo cual es sorprendente dado:
- su existencia surgiendo de extensions de extensions ;
- su papel en la construcción de un DSL usando dichas funciones de extensión;
-
la existencia de una
función de
biblioteca estándar
with
que, si no se tiene conocimiento de los receptores, puede parecer una palabra clave ; - Una sintaxis completamente separada para los tipos de función .
Todos estos temas tienen documentación, pero nada profundiza en los receptores.
Primero:
¿Qué es un receptor?
Cualquier bloque de código en Kotlin puede tener un (o incluso varios) tipos como receptor , haciendo que las funciones y propiedades del receptor estén disponibles en ese bloque de código sin calificarlo.
Imagine un bloque de código como este:
{ toLong() }
No tiene mucho sentido, ¿verdad?
De hecho, asignar esto a un
tipo
de
función
de
(Int) -> Long
- donde
Int
es el (único) parámetro y el tipo de retorno es
Long
- con razón daría lugar a un error de compilación.
Puede solucionar esto simplemente calificando la llamada a la función con el parámetro único implícito.
Sin embargo, para la construcción de DSL, esto causará un montón de problemas:
-
Los bloques anidados de DSL tendrán sus capas superiores sombreadas:
html { it.body { // how to access extensions of html here? } ... }
Esto puede no causar problemas para un HTML DSL, pero puede causar otros casos de uso. -
Puede ensuciar el código con
it
llamadas, especialmente para lambdas que usan mucho su parámetro (que pronto será receptor).
Aquí es donde entran en juego los receptores .
Al asignar este bloque de código a un tipo de función que tiene
Int
como
receptor
(¡no como parámetro!), El código de repente compila:
val intToLong: Int.() -> Long = { toLong() }
¿Que está pasando aqui?
Una pequeña nota al margen
Este tema supone familiaridad con los tipos de funciones , pero se necesita una pequeña nota al margen para los receptores.
Los tipos de función también pueden tener un receptor, prefijándolo con el tipo y un punto. Ejemplos:
Int.() -> Long // taking an integer as receiver producing a long
String.(Long) -> String // taking a string as receiver and long as parameter producing a string
GUI.() -> Unit // taking an GUI and producing nothing
Tales tipos de funciones tienen su lista de parámetros prefijada con el tipo de receptor.
Resolviendo código con receptores
En realidad, es increíblemente fácil entender cómo se manejan los bloques de código con receptores:
Imagine que, similar a las funciones de extensión, el bloque de código se evalúa dentro de la clase del tipo de receptor. this efectivamente se modifica por el tipo de receptor.
Para nuestro ejemplo anterior,
val intToLong: Int.() -> Long = { toLong() }
, resulta efectivamente que el bloque de código se evalúa en un contexto diferente, como si estuviera ubicado en una función dentro de
Int
.
Aquí hay un ejemplo diferente que usa tipos artesanales que lo muestran mejor:
class Bar
class Foo {
fun transformToBar(): Bar = TODO()
}
val myBlockOfCodeWithReceiverFoo: (Foo).() -> Bar = { transformToBar() }
efectivamente se convierte (en la mente, no en el código sabio - en realidad no puede extender las clases en la JVM):
class Bar
class Foo {
fun transformToBar(): Bar = TODO()
fun myBlockOfCode(): Bar { return transformToBar() }
}
val myBlockOfCodeWithReceiverFoo: (Foo) -> Bar = { it.myBlockOfCode() }
Observe cómo, dentro de una clase, no necesitamos usar
this
para acceder a
transformToBar
: lo mismo sucede en un bloque con un receptor.
Sucede que la documentación sobre this también explica cómo usar un receptor más externo si el bloque de código actual tiene dos receptores, a través de un calificado .
Espera, ¿múltiples receptores?
Sí. Un bloque de código puede tener múltiples receptores, pero actualmente no tiene expresión en el sistema de tipos. La única forma de archivar esto es a través de múltiples funciones de orden superior que toman un solo tipo de función de receptor. Ejemplo:
class Foo
class Bar
fun Foo.functionInFoo(): Unit = TODO()
fun Bar.functionInBar(): Unit = TODO()
inline fun higherOrderFunctionTakingFoo(body: (Foo).() -> Unit) = body(Foo())
inline fun higherOrderFunctionTakingBar(body: (Bar).() -> Unit) = body(Bar())
fun example() {
higherOrderFunctionTakingFoo {
higherOrderFunctionTakingBar {
functionInFoo()
functionInBar()
}
}
}
Tenga en cuenta que si esta característica del lenguaje Kotlin parece inapropiada para su DSL, ¡ @DslMarker es su amigo!
Conclusión
¿Por qué importa todo esto? Con este conocimiento:
-
ahora comprende por qué puede escribir
toLong()
en una función de extensión en un número, en lugar de tener que hacer referencia al número de alguna manera. ¿Quizás su función de extensión no debería ser una extensión? - Puede crear un DSL para su lenguaje de marcado favorito, tal vez ayudar a analizar uno u otro (¡¿ quién necesita expresiones regulares?! ).
- Usted comprende por qué existe una función de biblioteca estándar y no una palabra clave: el acto de enmendar el alcance de un bloque de código para ahorrar en tipeo reductor es tan común que los diseñadores de lenguaje lo ubican en la biblioteca estándar.
- (tal vez) aprendiste un poco sobre los tipos de funciones en la rama.
var greet: String.() -> Unit = { println("Hello $this") }
esto define una variable de
tipo
String.() -> Unit
, que le dice
-
String
es el receptor -
() -> Unit
es el tipo de función
Como mencionó anteriormente, todos los métodos de este receptor se pueden llamar en el cuerpo del método.
Entonces, en nuestro ejemplo,
this
se usa para imprimir la
String
.
La función se puede invocar escribiendo ...
greet("Fitzgerald") // result is "Hello Fitzgerald"
el fragmento de código anterior fue tomado de Kotlin Function Literals with Receiver - Introducción rápida por Simon Wirtz.