Volver a Kotlin Básico

Introducción

Kotlin soporta programación funcional con lambdas y funciones de orden superior, haciendo el código más conciso y expresivo.

Expresiones Lambda

Sintaxis Básica:

val suma = { a: Int, b: Int -> a + b } println(suma(3, 5)) // 8 val saludar = { nombre: String -> "Hola, $nombre!" } println(saludar("Alicia")) // Hola, Alicia! // Parámetro único puede usar 'it' val doble = { num: Int -> num * 2 } val dobleCorto: (Int) -> Int = { it * 2 }

Inferencia de Tipos:

// Tipo especificado val multiplicar: (Int, Int) -> Int = { a, b -> a * b } // Tipo inferido del contexto fun calcular(x: Int, y: Int, operacion: (Int, Int) -> Int): Int { return operacion(x, y) } calcular(5, 3) { a, b -> a + b } // 8

Funciones de Orden Superior

Funciones que toman funciones como parámetros o retornan funciones:

fun operarEnNumeros(a: Int, b: Int, operacion: (Int, Int) -> Int): Int { return operacion(a, b) } val resultado1 = operarEnNumeros(10, 5) { x, y -> x + y } // 15 val resultado2 = operarEnNumeros(10, 5) { x, y -> x * y } // 50

Retornando Funciones:

fun crearMultiplicador(factor: Int): (Int) -> Int { return { numero -> numero * factor } } val triple = crearMultiplicador(3) println(triple(5)) // 15 val doble = crearMultiplicador(2) println(doble(7)) // 14

Funciones de Orden Superior Comunes

forEach (para cada):

val numeros = listOf(1, 2, 3, 4, 5) numeros.forEach { println(it) } val nombres = listOf("Alicia", "Bob", "Carlos") nombres.forEach { nombre -> println("Hola, $nombre") }

map (transformar):

val numeros = listOf(1, 2, 3, 4, 5) val cuadrados = numeros.map { it * it } // [1, 4, 9, 16, 25] data class Persona(val nombre: String, val edad: Int) val personas = listOf(Persona("Alicia", 25), Persona("Bob", 30)) val nombres = personas.map { it.nombre } // [Alicia, Bob]

filter (filtrar):

val numeros = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) val pares = numeros.filter { it % 2 == 0 } // [2, 4, 6, 8, 10] val nombres = listOf("Alicia", "Bob", "Carlos", "David") val nombresLargos = nombres.filter { it.length > 4 } // [Alicia, Carlos, David]

reduce y fold:

val numeros = listOf(1, 2, 3, 4, 5) val suma = numeros.reduce { acc, n -> acc + n } // 15 val producto = numeros.fold(1) { acc, n -> acc * n } // 120 // Ejemplo más complejo val concatenado = listOf("a", "b", "c").fold("Inicio") { acc, s -> "$acc->$s" } // Inicio->a->b->c

any, all, none:

val numeros = listOf(1, 2, 3, 4, 5) val tienePar = numeros.any { it % 2 == 0 } // true val todoPositivos = numeros.all { it > 0 } // true val ningunNegativo = numeros.none { it < 0 } // true

find y firstOrNull (buscar y primer elemento o nulo):

val numeros = listOf(1, 2, 3, 4, 5) val primerPar = numeros.find { it % 2 == 0 } // 2 val primerGrande = numeros.firstOrNull { it > 10 } // null

Tipos de Función con Receptor

fun construirCadena(accion: StringBuilder.() -> Unit): String { val constructor = StringBuilder() constructor.accion() return constructor.toString() } val resultado = construirCadena { append("Hola") append(" ") append("Mundo") } println(resultado) // Hola Mundo

Funciones Inline

Las lambdas crean objetos, pero inline evita ese overhead:

inline fun medirTiempo(bloque: () -> Unit): Long { val inicio = System.currentTimeMillis() bloque() val fin = System.currentTimeMillis() return fin - inicio } val tiempo = medirTiempo { // Alguna operación Thread.sleep(100) } println("Tomó $tiempo ms")

Closures

Las lambdas pueden capturar variables de su contexto:

fun crearContador(): () -> Int { var cuenta = 0 return { ++cuenta } } val contador = crearContador() println(contador()) // 1 println(contador()) // 2 println(contador()) // 3

Closures Mutables:

var suma = 0 val numeros = listOf(1, 2, 3, 4, 5) numeros.forEach { suma += it } println(suma) // 15

Ejemplos Prácticos

Manejo de Eventos:

class Boton { private var clickListener: (() -> Unit)? = null fun onClick(listener: () -> Unit) { clickListener = listener } fun click() { clickListener?.invoke() } } val boton = Boton() boton.onClick { println("Botón clickeado!") } boton.click() // Botón clickeado!

Validación:

data class Usuario(val nombre: String, val email: String, val edad: Int) typealias Validador<T> = (T) -> Boolean fun <T> validar(valor: T, validadores: List<Validador<T>>): Boolean { return validadores.all { it(valor) } } val usuario = Usuario("Alicia", "[email protected]", 25) val validadoresUsuario = listOf<Validador<Usuario>>( { it.nombre.isNotBlank() }, { it.email.contains("@") }, { it.edad >= 18 } ) val esValido = validar(usuario, validadoresUsuario) // true

DSL (Lenguaje Específico de Dominio):

class HTML { private val contenido = StringBuilder() fun head(init: Head.() -> Unit) { val head = Head() head.init() contenido.append("<head>${head.contenido}</head>") } fun body(init: Body.() -> Unit) { val body = Body() body.init() contenido.append("<body>${body.contenido}</body>") } override fun toString() = "<html>$contenido</html>" } class Head { var contenido = "" fun title(texto: String) { contenido += "<title>$texto</title>" } } class Body { var contenido = "" fun h1(texto: String) { contenido += "<h1>$texto</h1>" } fun p(texto: String) { contenido += "<p>$texto</p>" } } fun html(init: HTML.() -> Unit): HTML { val html = HTML() html.init() return html } val pagina = html { head { title("Mi Página") } body { h1("Bienvenido") p("Este es un párrafo") } }

Lógica de Reintento:

inline fun <T> reintentar( veces: Int, retraso: Long = 1000, bloque: () -> T ): T? { repeat(veces) { intento -> try { return bloque() } catch (e: Exception) { if (intento == veces - 1) throw e Thread.sleep(retraso) } } return null } // Uso val resultado = reintentar(veces = 3) { // Llamada de red u operación que podría fallar obtenerDatosDeAPI() }

Buenas Prácticas

  • Usa it para lambdas de parámetro único cuando sea claro
  • Nombra parámetros lambda cuando mejore la legibilidad
  • Usa referencias de función cuando sea posible: ::nombreFuncion
  • Prefiere funciones de orden superior sobre bucles para operaciones de colección
  • Usa inline para funciones que toman lambdas para evitar overhead
  • Mantén las lambdas cortas y enfocadas

Patrones Comunes

Composición de Funciones:

fun <A, B, C> componer(f: (B) -> C, g: (A) -> B): (A) -> C { return { x -> f(g(x)) } } val sumarUno: (Int) -> Int = { it + 1 } val duplicar: (Int) -> Int = { it * 2 } val sumarUnoLuegoDuplicar = componer(duplicar, sumarUno) println(sumarUnoLuegoDuplicar(5)) // 12

Currying:

fun currySumar(a: Int): (Int) -> Int { return { b -> a + b } } val sumar5 = currySumar(5) println(sumar5(3)) // 8 println(sumar5(10)) // 15

Conclusión

Las lambdas y funciones de orden superior son características poderosas en Kotlin que habilitan patrones de programación funcional. Hacen el código más conciso, expresivo y flexible.

En el próximo capítulo, exploraremos las coroutines para programación asíncrona.