kotlin - studio - Definición de la función: diversión vs val
image android studio (2)
Tengo curiosidad acerca de cuál es la forma sugerida para definir las funciones de los miembros en Kotlin. Considere estas dos funciones miembro:
class A {
fun f(x: Int) = 42
val g = fun(x: Int) = 42
}
Estos parecen lograr lo mismo, pero encontré diferencias sutiles.
La definición basada en val
, por ejemplo, parece ser más flexible en algunos escenarios. Es decir, no podría encontrar una manera directa de componer f
con otras funciones, pero sí podría hacerlo con g
. Para funKTionale con estas definiciones, utilicé la biblioteca funKTionale . Encontré que esto no se compila:
val z = g andThen A::f // f is a member function
Pero si f
se definiera como un val
apunta a la misma función, se compilaría bien. Para averiguar qué estaba pasando, le pedí a IntelliJ que definiera explícitamente el tipo de ::f
y g
para mí, y me da esto:
val fref: KFunction1<Int, Int> = ::f
val gref: (Int) -> Int = g
Entonces, uno es de tipo KFunction1<Int, Int>
, el otro es de tipo (Int) -> Int
. Es fácil ver que ambos representan funciones de tipo Int -> Int
.
¿Cuál es la diferencia entre estos dos tipos y en qué casos importa? Noté que para las funciones de nivel superior, puedo componerlas bien usando cualquiera de las dos definiciones, pero para hacer la compilación antes mencionada, tuve que escribirla así:
val z = g andThen A::f.partially1(this)
Es decir, tuve que aplicarlo parcialmente a this
primero.
Ya que no tengo que pasar por esta molestia cuando uso val
s para funciones, ¿hay alguna razón por la que deba definir funciones que no sean miembros de Unit con fun
? ¿Hay alguna diferencia en el rendimiento o la semántica que me estoy perdiendo?
Aquí hay algunos códigos que muestran cómo f y g son diferentes cuando se trata de uso:
fun main(args: Array<String>) {
val a = A()
exe(a.g) // OK
//exe(a.f) // does not compile
exe { a.f(it) } // OK
}
fun exe(p: (Int) -> Int) {
println(p(0))
}
Puedes ver que g es un objeto que puede usarse como un lambda, pero f no puede. Para usar f de manera similar, tienes que envolverlo en un lambda.
Kotlin tiene que ver con la interoperabilidad de Java y la definición de una función como val
producirá un resultado completamente diferente en términos de interoperabilidad. La siguiente clase de Kotlin:
class A {
fun f(x: Int) = 42
val g = fun(x: Int) = 42
}
es efectivamente equivalente a:
public class A {
private final Function1<Integer, Integer> gref = new Function1<Integer, Integer>() {
@Override
public Integer invoke(final Integer integer) {
return 42;
}
};
public int f(final int value) {
return 42;
}
public Function1<Integer, Integer> getG() {
return gref;
}
}
Como puedes ver, las principales diferencias son:
-
fun f
es solo un método habitual, mientras queval g
de hecho es una función de orden superior que devuelve otra función -
val g
implica la creación de una nueva clase que no es buena si está apuntando a Android -
val g
requiere boxeo innecesario y unboxing -
val g
no se puede invocar fácilmente desde java:A().g(42)
en Kotlin vsnew A().getG().invoke(42)
en Java
ACTUALIZAR:
Respecto a la sintaxis de A::f
. El compilador generará una clase extra Function2<A, Integer, Integer>
para cada aparición de A::f
, por lo que el siguiente código da como resultado dos clases adicionales con 7 métodos cada una:
val first = A::f
val second = A::f
El compilador de Kotlin no es lo suficientemente inteligente en este momento para optimizar este tipo de cosas. Puede votar por el problema aquí https://youtrack.jetbrains.com/issue/KT-9831 . En caso de que esté interesado, aquí se muestra cómo se ve cada clase en el código de bytes: https://gist.github.com/nsk-mironov/fc13f2075bfa05d8a3c3