En Kotlin, dado que la palabra clave 'reificada' solo se puede usar para parámetros de tipo genérico de funciones en línea, ¿por qué tener la palabra clave reificada? ¿Por qué el compilador de Kotlin (al menos en el futuro) no puede considerar automáticamente todos los parámetros de tipo genérico de las funciones en línea como cosificados?
He visto que la gente entra en pánico cuando mira esta palabra 'reificada' y me pide que no complique el código. De ahí la pregunta.
Los parámetros de tipo cosificados requieren argumentos de tipo pasados en ellos para ser cosificados también. A veces es un requisito imposible (por ejemplo, los parámetros de clase no se pueden cosificar), por lo que hacer que todos los parámetros de las funciones en línea se cosifiquen de forma predeterminada haría imposible llamar a TODAS las funciones en línea en los casos en que ahora solo es imposible llamar a las que tienen un tipo cosificado. parámetros:
inline fun<T> genericFun(x: T) {} inline fun<reified T> reifiedGenericFun(x: T) {} class SimpleGenericClass<T>() { fun f(x: T) { genericFun<T>(x) //compiles fine reifiedGenericFun<T>(x) //compilation error } }ACTUALIZAR. ¿Por qué no inferir automáticamente "reificibilidad" según el contexto?
T::class (también agregué llamadas is T ). Aquí hay un contraejemplo para ambos: "a" as? T La función con este cuerpo tendría una semántica diferente dependiendo de si su parámetro de tipo se declara o no (o, hipotéticamente, se infiere) como cosificado:
inline fun<reified T> castToReifiedGenericType() = "a" as? T inline fun<T> castToSimpleGenericType() = "a" as? T fun main() { println(castToReifiedGenericType<Int>()) //null println(castToSimpleGenericType<Int>()) //a } /*PS unsafe cast ("a" as T) also have different semantics for reified and non-reified T, causing `ClassCastException` in the first case and still returning "a" in the latter.*/ Entonces, con el primer enfoque, la semántica cambiaría si agregamos una llamada sin sentido a T::class / is T en algún lugar dentro de la función en línea. Con el segundo, la semántica cambiaría si llamamos a esta función desde el nuevo sitio (donde T no se puede reified , mientras que antes era "reificable") o, por el contrario, eliminamos una llamada de este sitio (permitiendo que se reified ) .
La depuración de problemas que surgen de estas acciones (a primera vista, sin relación con la observación de cambios semánticos) es mucho más compleja y provoca pánico que agregar/leer una palabra clave reified explícita.
Como demuestra la respuesta de @ Михаил Нафталь, un tipo que se reified es muy limitante, por lo que es fundamental que el lenguaje requiera que sea explícito sobre exactamente qué tipos se deben cosificar. La reificación requiere que se conozca el tipo en el momento de la compilación, por lo que las funciones con tipos reificados solo se pueden llamar desde funciones donde ese tipo no es un tipo genérico no reificado.
Alguien podría argumentar, bueno, solo asumir que está cosificado si T::class se usa dentro de esta función en línea y, de lo contrario, tratarlo como no cosificado. Pero eso significaría que la firma de su función efectiva podría cambiar simplemente cambiando el contenido de la función sin cambiar su declaración. Eso haría muy fácil cambiar accidentalmente la firma de una función, lo cual es una receta para el desastre en el futuro. Por ejemplo, supongamos que tengo esta función:
inline fun <T> registerList(list: List<T>) { // T is not reified myProtectedRegistryList.add(list) }y así se usa en otros lugares de mi aplicación, o por usuarios de mi biblioteca como este:
class Foo<T>(val data: List<T>) { init { registerList(data) } } // or fun foo(data: List<T>) { registerList(data) } // or class Bar<T> { var barRegister: (List<T>)->Unit = ::registerList }Luego modifico mi función sin cambiar su declaración:
// In hypothetical Kotlin with implicit reification, T is now reified: inline fun <T> registerList(list: List<T>) { // This line of code unchanged! myProtectedRegistryMap.put(T::class, list) }Ahora he descifrado el código en todas partes donde se usó como uno de los ejemplos anteriores. Entonces, por el lenguaje que requiere que cambie la declaración para cambiar la firma, se ve obligado a pensar en el impacto externo de modificar su función. Usted sabe que si refactoriza la declaración de cualquier función, esa es la única forma en que su usabilidad se ve afectada en los sitios de llamadas.
La filosofía de diseño de Kotlin en este tipo de asuntos es ser conservador y requerir declaraciones explícitas. Es la misma razón por la que las interfaces funcionales deben marcarse explícitamente con la palabra clave fun incluso si actualmente solo tienen una única función abstracta, y las clases/funciones son definitivas de forma predeterminada.
Aceptando la respuesta de @Михаил Нафталь (la primera proporcionada y actualizada) con un cálido agradecimiento también a @Tenfour04. Solo pensé en agregar una respuesta (con suerte) más simplificada basada en mi comprensión de las respuestas proporcionadas, que aún sería esencialmente correcta:
List<T> , podemos evaluar T::class.java a partir de él. Tal tipo verificable probablemente no haya tenido borrado de tipo, eso es algo que el compilador de Java hace en los parámetros de tipo para reducir el tamaño del binario compilado. Parece que en la actualidad, hay ciertos casos en los que el borrado de tipo lo realiza el compilador de Java heredado, etc. y kotlin aún no proporciona una forma de personalizar eso (es decir, evitarlo en ciertos casos).List<T> no se puede determinar si el tipo es verificable o no. Es como si se pudiera haber agregado una palabra clave para que el desarrollador vea esto explícitamente, por ejemplo: List<nonerased T> and List<erased T> . Pero en la actualidad, el compilador detecta esto en función de dónde/cómo se definió T, de lo contrario, el lenguaje podría volverse demasiado detallado.inline fun <T> f1(list: List<T>){...} , hay una opción para declarar un tipo como reified T (lo que lo hace inline fun <reified T> f1(list: List<T>){...} ), lo que significa que aceptará solo el tipo de parámetros List<nonerased T> (que tienen esta palabra clave invisible 'nonerased' asociada con ellos), de lo contrario, dará un error de compilación. Entonces no borrará el tipo para T. Debido a esta verificación en tiempo de compilación, el usuario ahora puede proceder a usar T::class.java dentro de la función en línea, con la seguridad de que no obtendrá un error en tiempo de ejecución porque T se erased T , por lo que no se puede encontrar T::class.java .