Kotlin DSL: A Stardew valley story I

Introducción al desarrollo de DSLs en Kotlin, utilizando conceptos clave como lambdas y funciones de orden superior, con ejemplos inspirados en Stardew Valley.

En este artículo se mostrarán los conocimientos de Kotlin necesarios para el desarrollo de un DSL en este lenguaje. Si para ti es «hace 3 años que no se me muere una planta» estos conceptos no dejes de leerlo ya que nunca está de más un repasito o una lectura rápida. Si aún así consideras que no te hace falta puedes avanzar directamente al artículo

Text
Kotlin DSL: A Stardew valley story II

, donde aplicaremos estos conocimientos para mostrar la información de nuestra granja. Y si eres de las personas que se les muere hasta los cactus, no te preocupes, esta sencilla explicación te podrá ayudar a entenderlo mejor. Se usarán ejemplos cogiendo de base el mundo del juego de Stardew Valley.

Conceptos básicos de Kotlin para el desarrollo de un DSL

¿Qué es un DSL?

DSL es el acrónimo de «Domain Specific Language» es un lenguaje de programación específico para un dominio concreto. Es decir, un DSL es un lenguaje de programación diseñado para resolver problemas en un ámbito particular, como el desarrollo web (HTML), la automatización de tareas, etc. Los DSLs te ayudan a simplificar la escritura y la legibilidad del código.

Por otro lado, GSL (General Purpose Language) es un lenguaje de programación general que se utiliza para resolver problemas en una amplia variedad de dominios. Probablemente cualquiera de los lenguajes de programación que usas en tu día a día Java, Python o Kotlin.

Ahora, quizás, estarás pensando por qué me estás contando todo esto y qué puedo hacer con ello. No te preocupes que te lo explico y no pretendas plantar una chirivía cuando todavía estás a día 27 de invierno. Muy sencillo, los DSLs se pueden implementar en GSLs, como en Kotlin, para definir acciones (dominios específicos) o configuraciones de forma más sencilla (builds scripts de Gradle y dejar de lado a Groovy) y legible.

En el caso concreto de Kotlin, un DSL puede ser utilizado para definir acciones o configuraciones de forma más sencilla y legible. Por ejemplo, un DSL para definir acciones en un juego podría tener una sintaxis similar a la siguiente:


Text
character {
    player {
        name = "Alice"
        level = 10
        live = 9
        damage = 3
    }
    enemy {
        name = "Dragon"
        level = 20
        live = 15
        damage = 5
    }
}

Seguramente esta estructura que acabamos de ver en el ejemplo te suene de algo, ¿verdad? Pues sí, es muy similar a la estructura de un archivo XML o JSON. Y es que los DSLs se pueden implementar de diferentes maneras como, por ejemplo, utilizando funciones de ámbito, funciones de extensión, lambdas con receptor, entre otros. Pero no adelantemos acontecimientos.

Entonces, centrándonos en la materia y asumiendo que en este punto ya conocemos la programación orientada a objetos, para crear nuestro DSL en Kotlin será necesario tener estos conocimientos:

  • Lambdas y lambdas con receptor
  • Funciones de orden superior, de extensión y ámbito
  • Patrón de diseño Builder

Y si además queremos hacerlo más idiomático y eficiente es recomendable conocer:

  • Notaciones infix
  • Sobrecarga de operadores
  • Funciones inline
  • Patrón (o anti-patrón) de diseño Singleton

Lambdas

Las lambdas son funciones anónimas que pueden ser pasadas como argumentos a otras funciones. En términos generales, una lambda se centra en describir un algoritmo, comportamiento, acción o procedimiento. No es más que el cuerpo de una función tradicional que hemos hecho miles de veces.

En conclusión, una lambda puede definirse como un proceso que, a partir de determinadas entradas, produce un resultado.

La estructura de las lambdas en Kotlin se definen entre llaves

Text
{}

y se pueden asignar a variables.


Text
val energyProvider: (String) -> String = { element -> "$element provides energy!" }

fun main() {
    val result = energyProvider("Barn")
    println(result)
}

// Result: Barn provides energy!

Y en el caso de que la lambda tenga más de un parámetro, se definirán entre paréntesis

Text
()

. ¿Se pueden tener 20 parámetros? Sí, pero más no significa mejor. Tanto aquí como cuando desarrollamos en cualquier lenguaje, la legibilidad y mantenibilidad del código es lo más importante.


Text
val energyProvider: (String, Int) -> String = { element, value -> "$element provides $value energy!" }

fun main() {
    val result = energyProvider("Barn", 10)
    println(result)
}

// Result: Barn provides 10 energy!

Funciones de extensión

Las funciones de extensión permiten añadir nuevas funciones a una clase sin modificar su código fuente. Una función de extensión es una función común cuyo nombre lleva como prefijo el nombre de la clase que se quiere extender, conectados ambos con un punto

Text
CLASS.function

.

Para Kotlin, las funciones de extensión se definen con la palabra clave

Text
fun

seguida del nombre de la clase a la que se quiere añadir la función y el cuerpo de la función

Text
fun CLASS.function

. Estas funciones se definen fuera de la clase de la que se está extendiendo, pero se pueden invocar como si fueran métodos de la instancia de la clase. Esta particularidad nos viene bien cuando queremos añadir funcionalidades a clases que no controlamos, como las clases de las librerías que usamos.


Text
val provide: (String, Int) -> String = { element, value ->
    "$element provides $value energy!"
} // Lambda que proporciona energía

val consume: (String, Int) -> String = { element, value ->
    "$element consumes $value energy!"
} // Lambda que consume energía

fun String.provideEnergy(energy: Int): String {
    return "$this provides $energy energy!"
} // Función de extensión para String que proporciona energía

fun String.consumeEnergy(energy: Int): String {
    return "$this consumes $energy energy!"
} // Función de extensión para String que consume energía

fun main() {
    val barnEnergy = "Barn".provideEnergy(50)
    val fieldEnergy = "Field".consumeEnergy(20)

    println("Barn Energy: $barnEnergy")
    println("Field Energy: $fieldEnergy")
}

// Barn Energy: Barn provides 50 energy!
// Field Energy: Field consumes 20 energy!

Si te das cuenta, en nuestro desarrollo habitual podemos encontrar múltiples funciones de extensión, como

Text
String.toByteArray()

,

Text
String.toUpperCase()

,

Text
String.toLowerCase()

, etc. ¿Qué otras funciones de extensión conoces?

Funciones de orden superior

Las funciones de orden superior son funciones que reciben otras funciones como parámetros y pueden devolver funciones como resultado. En otras palabras, son funciones que tratan a otras funciones como si fueran datos. Es decir, estas funciones pueden declararse sin la necesidad de crear una clase que las contenga y pueden ser pasadas como argumentos a otras funciones. Esta característica permite que Kotlin pueda ser un fantástico híbrido de lenguaje ya que permite tanto la programación orientada a objetos como la programación funcional.

Otra particularidad de las funciones de orden superior es que pueden ser almacenadas en variables, pasadas como argumentos a otras funciones y devueltas como resultado de otras funciones.


Text
fun manageEnergy(element: String, energy: Int, operation: (String, Int) -> String): String {
    println("Calculating energy...")
    return operation(element, energy)
} // Función de orden superior que recibe una lambda

val provide: (String, Int) -> String = { element, value ->
    println("Providing energy...")
    "$element provides $value energy!"
} // Lambda que proporciona energía

val consume: (String, Int) -> String = { element, value ->
    println("Consuming energy...")
    "$element consumes $value energy!"
} // Lambda que consume energía

fun main() {
    val barnEnergy = manageEnergy("Barn", 50, provide)
    val fieldEnergy = manageEnergy("Field", 20, consume)

    println("Barn Energy: $barnEnergy")
    println("Field Energy: $fieldEnergy")
}

// Barn Energy: Barn provides 50 energy!
// Field Energy: Field consumes 20 energy!

Y te preguntarás, ¿pero esto no se parece mucho a una lambda? Sí, pero no o no pero sí. La diferencia, una de ellas, entre una función de orden superior y una lambda es que la primera es una función que recibe o devuelve otra función, mientras que la segunda es una función anónima que puede ser pasada como argumento a otra función.

Algunos de los operadores que solemos usar cuando trabajamos con colecciones como

Text
map

,

Text
filter

,

Text
reduce

,

Text
forEach

, entre otros, son funciones de orden superior.


Text
val numbers = listOf(1, 2, 3, 4, 5)

val squaredNumbers = numbers.map { it * it }

println(squaredNumbers) // [1, 4, 9, 16, 25]

Lambdas con receptor

Las lambdas con receptor son un tipo especial, como un Fruto Qi, de lambda que permiten acceder a las funciones y propiedades de un objeto receptor sin necesidad de hacer referencia explícita. Dicho de otra forma y para entender este trabalenguas, las lambdas con receptor permiten acceder a las funciones y propiedades de un objeto receptor como si fueran miembros de la lambda.

Por ejemplo, si tenemos una clase

Text
EnergyElement

con una propiedad

Text
element

y una función

Text
showStatus()

lo que podemos hacer es uso de una lambda con receptor para acceder a estas propiedades. También podemos hacer funciones sin hacer referencia explícita a la clase y sin necesidad de pasar el objeto como argumento quedando la función de la siguiente forma:


Text
class EnergyElement(var element: String, var energy: Int) {
    fun showStatus(): String {
        return "$element has $energy energy!"
    }
}

val manageEnergy: EnergyElement.() -> String = {
    if (energy > 0) {
        "$element provides $energy energy!"
    } else {
        "$element has no energy left!"
    }
}

fun main() {
    val barnEnergy = barn.manageEnergy()
    val fieldEnergy = field.manageEnergy()

    println("Barn Energy: $barnEnergy")
    println("Field Energy: $fieldEnergy")
}

// Barn Energy: Barn provides 50 energy!
// Field Energy: Field has no energy left!

Continuando la explicación anterior al ejemplo, la lambda

Text
manageEnergy

es una lambda con receptor que se aplica a la clase

Text
EnergyElement

. Al invocar la lambda

Text
manageEnergy

sobre una instancia de

Text
EnergyElement

, se puede acceder a las propiedades y funciones de la clase

Text
EnergyElement

sin necesidad de hacer referencia explícita a ella.

En conclusión, son la fusión de lambdas con funciones con extensión -también denominado como tipo de función de extensión-, así que como ya hemos visto anteriormente estos conceptos nos será más amigable aprender este.

Referencias

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad

Información básica acerca de la protección de datos

  • Responsable: IZERTIS S.A.
  • Finalidad: Envío información de carácter administrativa, técnica, organizativa y/o comercial sobre los productos y servicios sobre los que se nos consulta.
  • Legitimación: Consentimiento del interesado
  • Destinatarios: Otras empresas del Grupo IZERTIS. Encargados del tratamiento.
  • Derechos: Acceso, rectificación, supresión, cancelación, limitación y portabilidad de los datos.
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad

Ingeniera Informática y desarrolladora full-stack. Disfruto explorando tanto nuevas tecnologías como diferentes perspectivas y disciplinas. Creo que la mejora continua y un conocimiento común y accesible nos ayuda a entender el mundo en el que vivimos y nos permite ofrecer soluciones efectivas e innovadoras a los problemas que se nos presentan.

¿Quieres publicar en Adictos al trabajo?

Te puede interesar

02/03/2026

José Antonio Sánchez Segovia

Zephyr es un RTOS open source respaldado por la Linux Foundation que permite desarrollar dispositivos embebidos conectados, eficientes y escalables, facilitando el paso de prototipo a producto final con una arquitectura mantenible.

23/02/2026

Enrique Casado Díez

LoRa y LoRaWAN son tecnologías clave en el ecosistema IoT cuando se requiere largo alcance y bajo consumo energético. En este artículo analizamos su funcionamiento, Spreading Factor, link budget, arquitectura de red, frecuencias y clases de dispositivos, con un caso práctico real.

19/02/2026

Juan José Díaz Antuña

Copilot Chat es la forma más sencilla y segura de empezar a usar IA en Microsoft 365. En este artículo vemos cómo funciona, cómo activarlo y en qué se diferencia de Microsoft 365 Copilot, Copilot Studio y los Agentes Inteligentes, con ejemplos prácticos y una comparativa clara.