Lambdas

Una lambda es una función anónima que podés tratar como un valor — guardarla en una variable, pasarla como argumento, retornarla desde otra función:

// Sintaxis completa: { parámetros -> cuerpo }
val saludar = { nombre: String -> "Hola, $nombre!" }
println(saludar("Carlos"))  // Hola, Carlos!

// Lambda sin parámetros
val imprimirHola = { println("Hola!") }
imprimirHola()

// Lambda con múltiples parámetros
val sumar = { a: Int, b: Int -> a + b }
println(sumar(3, 4))  // 7

// Lambda de varias líneas — el último valor es el resultado
val procesarTexto = { texto: String ->
    val limpio = texto.trim().lowercase()
    val palabras = limpio.split(" ")
    palabras.joinToString("-")   // este es el valor de retorno
}
println(procesarTexto("  Hola Mundo  "))  // hola-mundo

Tipos de función

Las funciones tienen tipos, igual que Int o String. La sintaxis es (TipoParam1, TipoParam2) -> TipoRetorno:

// Tipo de función: recibe String, retorna String
val transformar: (String) -> String = { it.uppercase() }

// Recibe dos Int, retorna Int
val operar: (Int, Int) -> Int = { a, b -> a + b }

// Sin parámetros, sin retorno útil
val accion: () -> Unit = { println("ejecutado") }

// Nullable — la función puede ser null
var callback: (() -> Unit)? = null
callback?.invoke()   // safe call para invocar

// Tipos de función con receiver — la lambda tiene acceso a 'this'
// Se usan internamente en las scope functions
val configurar: StringBuilder.() -> Unit = {
    append("Hola ")
    append("Kotlin")
}
val sb = StringBuilder()
sb.configurar()
println(sb)  // Hola Kotlin

Funciones de orden superior

Una función de orden superior es una función que recibe otra función como parámetro o la retorna:

// Recibe una función como parámetro
fun operar(a: Int, b: Int, operacion: (Int, Int) -> Int): Int {
    return operacion(a, b)
}

operar(10, 3, { a, b -> a + b })   // 13
operar(10, 3, { a, b -> a * b })   // 30
operar(10, 3, { a, b -> a - b })   // 7

// Retorna una función
fun crearMultiplicador(factor: Int): (Int) -> Int {
    return { numero -> numero * factor }
}

val duplicar = crearMultiplicador(2)
val triplicar = crearMultiplicador(3)
println(duplicar(5))   // 10
println(triplicar(5))  // 15

// Caso real: ejecutar código con manejo de errores centralizado
fun  ejecutarSeguro(accion: () -> T): Result {
    return try {
        Result.success(accion())
    } catch (e: Exception) {
        Result.failure(e)
    }
}

val resultado = ejecutarSeguro { apiService.getProductos() }
resultado.onSuccess { productos -> mostrar(productos) }
resultado.onFailure { error -> mostrarError(error.message) }

Trailing lambda — la sintaxis que usás todo el tiempo

Si el último parámetro de una función es una lambda, podés sacarla de los paréntesis. Si es el único parámetro, los paréntesis desaparecen:

// Sin trailing lambda
listOf(1, 2, 3).filter({ it > 1 })

// Con trailing lambda — la lambda sale de los paréntesis
listOf(1, 2, 3).filter { it > 1 }

// Si hay más parámetros, solo la lambda sale
listOf(1, 2, 3).fold(0, { acc, i -> acc + i })
listOf(1, 2, 3).fold(0) { acc, i -> acc + i }  // más limpio

// Esto explica la sintaxis que ya conocés de Android:
viewModelScope.launch {          // launch recibe una lambda como último param
    val datos = repository.getDatos()
}

binding.btnGuardar.setOnClickListener {  // setOnClickListener igual
    viewModel.guardar()
}

Column {                         // los composables de Compose también
    Text("Hola")
    Button(onClick = { }) {
        Text("Click")
    }
}

Esto es lo que ya usabas sin saberloCada vez que escribís launch { }, setOnClickListener { } o un composable con contenido, estás usando trailing lambda. No es magia — es una función que recibe otra función como último parámetro.

it y destructuring en lambdas

// Cuando la lambda tiene un solo parámetro, se puede omitir y usar 'it'
listOf(1, 2, 3).filter { it > 1 }          // it es el Int
listOf("a", "b").map { it.uppercase() }    // it es el String

// Pero si la lambda es compleja, nombrá el parámetro explícitamente
listOf(usuarios).map { usuario ->          // más legible que 'it' para objetos
    "${usuario.nombre} (${usuario.email})"
}

// Ignorar parámetros con _
mapOf("a" to 1).forEach { (_, valor) -> println(valor) }

// Destructuring en lambdas
listOf(Pair("Ana", 25), Pair("Carlos", 30)).forEach { (nombre, edad) ->
    println("$nombre tiene $edad años")
}

Referencias a funciones

Podés pasar una función existente como valor usando :::

fun esPar(n: Int) = n % 2 == 0
fun imprimirMayus(texto: String) = println(texto.uppercase())

// En lugar de lambda
listOf(1, 2, 3, 4).filter { esPar(it) }
listOf("a", "b").forEach { imprimirMayus(it) }

// Con referencia a función — más limpio
listOf(1, 2, 3, 4).filter(::esPar)
listOf("a", "b").forEach(::imprimirMayus)
listOf("a", "b", "a").filter(::println)  // referencia a función del stdlib

// Referencia a método de instancia
data class Usuario(val nombre: String, val activo: Boolean)
val usuarios = listOf(Usuario("Ana", true), Usuario("Bob", false))

// En lugar de: usuarios.filter { it.activo }
val activos = usuarios.filter(Usuario::activo)
val nombres = usuarios.map(Usuario::nombre)

inline functions

Las lambdas generan objetos en el heap. Para funciones de orden superior que se llaman frecuentemente, inline copia el cuerpo de la función en el punto de llamada — sin overhead de objeto:

// Sin inline — crea un objeto lambda en cada llamada
fun medirTiempo(bloque: () -> Unit): Long {
    val inicio = System.currentTimeMillis()
    bloque()
    return System.currentTimeMillis() - inicio
}

// Con inline — el compilador copia el código, sin objeto lambda
inline fun medirTiempo(bloque: () -> Unit): Long {
    val inicio = System.currentTimeMillis()
    bloque()
    return System.currentTimeMillis() - inicio
}

// Las funciones del stdlib como filter, map, let, run son todas inline
// Por eso no tienen overhead aunque las uses millones de veces