Funciones de orden superior, anónimas y lambdas



En Kotlin se puede operar con las funciones al igual que se opera con otros valores que no son funciones: las funciones pueden almacenarse en variables y estructuras de datos, pasarse como argumentos y devolverse desde otras funciones de orden superior. Para facilitar esto, Kotlin utiliza un tipo específico de función para representar funciones y proporciona unas construcciones especializadas como las expresiones lambda. En esta entrada vamos a hacer una primera aproximación introductoria a las funciones de orden superior, anónimas y lambdas.

Funciones de orden superior

Las funciones de orden superior permiten tratar a las funciones como cualquier otro valor y por tanto pueden recibir como parámetros otras funciones y/o devolverlas como resultado. Un ejemplo:
fun calcular(n1: Int, n2: Int, operacion: (Int, Int) -> Int): Int {
    return operacion(n1, n2)
}

fun sumar(x: Int, y: Int) = x + y

fun restar(x: Int, y: Int) = x - y

fun multiplicar(x: Int, y: Int) = x * y

fun dividir(x: Int, y: Int) = x / y

fun main(parametro: Array<String>) {
    val resultado = calcular(20, 5, ::sumar)
    println("20 + 5 = $resultado") // 20 + 5 = 25
    println("10 - 4 = ${calcular(10, 4, ::restar)}") // 10 - 4 = 6
    println("12 * 2 = ${calcular(12, 2, ::multiplicar)}") // 12 * 2 = 24
    println("50 / 2 = ${calcular(50, 2, ::dividir)}") // 50 / 2 = 25    
}
En el código anterior tenemos una función de orden superior llamada calcular, con tres parámetros: dos de tipo entero (n1 y n2) y otro de tipo función (operacion). Cuando un parámetro es de tipo función debemos indicar los parámetros que tiene dicha función (en este caso dos parámetros enteros) y luego el tipo de dato que devuelve con el operador -> (si no devuelve ningún dato utilizamos el tipo Unit). Por su parte, la función calcular invoca a la función operacion pasándole sus dos propios parámetros enteros y devuelve el entero que resulta de dicha función. Después, cuando llamamos a la función calcular le pasamos tres datos: dos enteros y otro con referencia a una función para lo que antecedemos el operador ::.

Funciones anónimas

Las funciones anónimas (y también las expresiones lambda) son 'funciones literales', es decir, funciones que no se declaran pero se pasan de inmediato como una expresión.
fun main(args: Array<String>) {
    val multiplicarPorCinco = fun(a: Int) = a * 5 // función anónima
    println(multiplicarPorCinco(4)) // 20
}

Lambdas

Una lambda es una función anónima que se utiliza en el mismo momento que se declara. Utilizamos una expresión lambda para enviar directamente una función anónima a una función de orden superior, declarándola entre llaves y separando los parámetros del cuerpo de la función con el operador ->.
fun transformar(n: Int, anonima: (Int) -> Int) = anonima(n)

fun main(args : Array<String>) {
    val resultado = transformar(2, { a: Int -> a * 5 }) // lambda que devuelve un tipo inferido Int
    println(resultado) // 10
}
En este ejemplo, la función transformar recibe dos parámetros: un entero y una función que a su vez tiene como parámetro un entero y devuelve un entero. Además la función transformar devuelve el resultado de invocar a la función anonima con el primer parámetro. Hay que tener en cuenta que una lambda no permite especificar el tipo de retorno; normalmente se puede inferir, pero si necesitamos declararlo explícitamente tenemos que utilizar una función anónima.

Si el parámetro de la lambda se puede inferir, podemos prescindir del tipo en la declaración:
val resultado = transformar(2, { a -> a * 5 })
En este caso además, al ser un único parámetro, podemos ignorarlo en la declaración y utilizar el parámetro estándar it:
val resultado = transformar(2, { it * 5} )
El ejemplo anterior de la función de orden superior calcular lo podemos escribir de una forma más natural con lambdas:
fun calcular(n1: Int, n2: Int, operacion: (Int, Int) -> Int): Int{
    return operacion(n1, n2)
}

fun main(parametro: Array<String>) {
    val suma = calcular(20, 5, { x, y -> x + y })
    println(suma) // 25
    val resta = calcular(10, 4, { x, y -> x - y })
    println(resta) // 6
    var sumaCuadrados = calcular(2, 4, { x, y -> (x*x) + (y*y)})
    println(sumaCuadrados) // 20
    var media = calcular(2, 4, { x, y -> 
        val n = listOf(x, y).size
        val promedio = (x + y) / n            
        promedio
    })
    println(media) // 3
}
Podemos aprovechar la posibilidad de retornar funciones para hacer funciones que crean funciones:
fun mayorQue(n: Int) = { m: Int -> m > n }

fun main(args: Array<String>) {
    val mayorQue5 = mayorQue(5)
    val mayorQue10 = mayorQue(10)
    println(mayorQue10(1))  // false
    println(mayorQue10(15)) // true
}
Puedes ilustrar algunos de estos conceptos en la entrada Ejemplos de función anónima y lambda para crear un Array.

Comentarios

Entradas populares

Recursos gratis para aprender Kotlin

I/O: entrada y salida de datos en consola

Lectura y escritura de archivos