Metaprogramación en Kotlin: cómo hacer que el código escriba código
\n\nImagina que escribes código que escribe código. ¿Suena a ciencia ficción? En el mundo de la metaprogramación en Kotlin esto es una realidad. La metaprogramación es una técnica en la que un programa se manipula a sí mismo o a otros programas como datos. Para un desarrollador de Kotlin, esto es un superpoder que permite reducir toneladas de código repetitivo, crear frameworks flexibles y automatizar tareas rutinarias.
\n\nEn este artículo, exploraremos las técnicas avanzadas de metaprogramación en Kotlin: desde la reflexión clásica hasta la generación de código moderna con KSP. Aprenderás a usar tipos reificados, funciones inline, anotaciones y mucho más. ¡Vamos allá!
\n\nReflexión en Kotlin: mirando bajo el capó
\n\nLa reflexión es la base de la metaprogramación. En Kotlin se implementa a través de la biblioteca kotlin-reflect. Con ella, puedes obtener dinámicamente información sobre clases, funciones y propiedades durante la ejecución.
Ejemplo: supongamos que necesitas serializar un objeto a JSON sin conocer su tipo de antemano. La reflexión te permite hacerlo:
\n\nimport kotlin.reflect.full.memberProperties\n\nfun toJson(obj: Any): String {\n val sb = StringBuilder("{")\n val props = obj::class.memberProperties\n props.forEachIndexed { index, prop ->\n sb.append("\\"${prop.name}\\": \\"${prop.get(obj)}\\"")\n if (index < props.size - 1) sb.append(", ")\n }\n sb.append("}")\n return sb.toString()\n}\n\ndata class User(val name: String, val age: Int)\n\nfun main() {\n val user = User("Alice", 30)\n println(toJson(user)) // {"name": "Alice", "age": "30"}\n}\n\n\nEsta es una técnica básica pero poderosa. Sin embargo, la reflexión tiene una desventaja: el rendimiento. Para sistemas de alta carga, es mejor usar la generación de código, de la que hablaremos más adelante.
\n\nFunciones inline y tipos reificados
\n\nUna de las características más interesantes de Kotlin para la metaprogramación son los tipos reificados (reified). Solo funcionan dentro de funciones inline y permiten conservar la información del tipo durante la ejecución, algo que normalmente es imposible debido al borrado de tipos en la JVM.
\n\nObserva cómo se puede obtener el tipo en tiempo de ejecución de forma elegante:
\n\ninline fun <reified T> typeName(): String {\n return T::class.simpleName ?: "Unknown"\n}\n\nfun main() {\n println(typeName<String>()) // String\n println(typeName<List<Int>>()) // List\n}\n\n\nSin reified, este truco no funcionaría: el compilador simplemente borraría el tipo. Con esta técnica, puedes crear fábricas tipadas, serializadores e incluso frameworks completos. Esto es el tipo reificado en Kotlin en acción.
Anotaciones: tu propio DSL para metadatos
\n\nLas anotaciones en Kotlin no son solo marcadores. Combinadas con reflexión o generación de código, se convierten en una potente herramienta de metaprogramación. Creemos nuestra propia anotación para validación:
\n\n@Target(AnnotationTarget.FIELD)\n@Retention(AnnotationRetention.RUNTIME)\nannotation class NotBlank(val message: String = "El campo no debe estar vacío")\n\nfun validate(obj: Any): List<String> {\n val errors = mutableListOf<String>()\n obj::class.memberProperties.forEach { prop ->\n val annotation = prop.findAnnotation<NotBlank>()\n if (annotation != null) {\n val value = prop.get(obj) as? String\n if (value.isNullOrBlank()) {\n errors.add("${prop.name}: ${annotation.message}")\n }\n }\n }\n return errors\n}\n\ndata class Form(val name: String, @NotBlank val email: String)\n\nfun main() {\n val form = Form("Iván", "")\n println(validate(form)) // [email: El campo no debe estar vacío]\n}\n