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
itpara 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
inlinepara 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.