Las funciones de Kotlin son primera clase, lo que significa que pueden almacenarse en variables y estructuras de datos, pasarse como argumentos y devolverse desde otras funciones de orden superior. Puede operar con funciones de cualquier forma que sea posible para otros valores que no sean de función.

Para facilitar esto, Kotlin, como lenguaje de programación de tipo estático, utiliza una familia de tipos de funciones para representar funciones y proporciona un conjunto de construcciones de lenguaje especializadas, como expresiones lambda.

Funciones de orden superior

Una función de orden superior es una función que toma funciones como parámetros o devuelve una función.

Un buen ejemplo es el lenguaje de programación funcional fold para colecciones, que toma un valor de acumulador inicial y una función de combinación y construye su valor de retorno combinando consecutivamente el valor de acumulador actual con cada elemento de colección, reemplazando el acumulador:

fun<T, R> Collection<T>.fold(
    initial: R, 
    combine:(acc: R, nextElement: T)-> R
): R var accumulator: R = initial
    for(element: T inthis)
        accumulator =combine(accumulator, element)return accumulator

En el código anterior, el parámetro combine tiene un tipo de función(R, T) -> R, por lo que acepta una función que toma dos argumentos de tipos R y T y devuelve un valor de tipo R. Se invoca dentro del por-loop, y el valor de retorno se asigna a accumulator.

Llamar fold, debemos pasarle una instancia del tipo de función como argumento, y las expresiones lambda (descritas con más detalle a continuación) se usan ampliamente para este propósito en sitios de llamadas de función de orden superior:

funmain()//sampleStartval items =listOf(1,2,3,4,5)// Lambdas are code blocks enclosed in curly braces.
    items.fold(0,// When a lambda has parameters, they go first, followed by '->'
        acc: Int, i: Int ->print("acc = $acc, i = $i, ")val result = acc + i
        println("result = $result")// The last expression in a lambda is considered the return value:
        result
    )// Parameter types in a lambda are optional if they can be inferred:val joinedToString = items.fold("Elements:", acc, i -> acc +" "+ i )// Function references can also be used for higher-order function calls:val product = items.fold(1, Int::times)//sampleEndprintln("joinedToString = $joinedToString")println("product = $product")

Las siguientes secciones explican con más detalle los conceptos mencionados hasta ahora.

Tipos de funciones

Kotlin usa una familia de tipos de funciones como (Int) -> String para declaraciones que se ocupan de funciones: val onClick: () -> Unit = ....

Estos tipos tienen una notación especial que corresponde a las firmas de las funciones, es decir, sus parámetros y valores de retorno:

  • Todos los tipos de funciones tienen una lista de tipos de parámetros entre paréntesis y un tipo de retorno: (A, B) -> C denota un tipo que representa funciones tomando dos argumentos de tipos A y B y devolviendo un valor de tipo C. La lista de tipos de parámetros puede estar vacía, como en () -> A. los Unit el tipo de retorno no se puede omitir.

  • Los tipos de función pueden tener opcionalmente un receptor tipo, que se especifica antes de un punto en la notación: el tipo A.(B) -> C representa funciones que se pueden llamar en un objeto receptor de A con un parámetro de B y devuelve un valor de C. Los literales de función con receptor se utilizan a menudo junto con estos tipos.

  • Las funciones de suspensión pertenecen a tipos de funciones de un tipo especial, que tienen un suspender modificador en la notación, como suspend () -> Unit o suspend A.(B) -> C.

La notación del tipo de función puede incluir opcionalmente nombres para los parámetros de la función: (x: Int, y: Int) -> Point. Estos nombres se pueden utilizar para documentar el significado de los parámetros.

Para especificar que un tipo de función es anulable, use paréntesis: ((Int, Int) -> Int)?.

Los tipos de funciones se pueden combinar usando paréntesis: (Int) -> ((Int) -> Unit)

La notación de flecha es asociativa a la derecha, (Int) -> (Int) -> Unit es equivalente al ejemplo anterior, pero no a ((Int) -> (Int)) -> Unit.

También puede asignar un nombre alternativo a un tipo de función mediante el uso de un alias de tipo:

typealias ClickHandler =(Button, ClickEvent)-> Unit

Instanciar un tipo de función

Hay varias formas de obtener una instancia de un tipo de función:

  • Usando un bloque de código dentro de una función literal, en una de las formas:
    • una expresión lambda: a, b -> a + b ,
    • una función anónima: fun(s: String): Int return s.toIntOrNull() ?: 0

    Los literales de función con receptor se pueden utilizar como valores de tipos de función con receptor.

  • Usando una referencia invocable a una declaración existente:
    • una función de nivel superior, local, miembro o de extensión: ::isOdd, String::toInt,
    • una propiedad de nivel superior, miembro o extensión: List::size,
    • un constructor: ::Regex

    Estos incluyen referencias enlazadas que se pueden llamar que apuntan a un miembro de una instancia en particular: foo::toString.

  • Usando instancias de una clase personalizada que implementa un tipo de función como interfaz:
class IntTransformer:(Int)-> Int overrideoperatorfuninvoke(x: Int): Int =TODO()val intFunction:(Int)-> Int =IntTransformer()

El compilador puede inferir los tipos de función para las variables si hay suficiente información:

val a = i: Int -> i +1// The inferred type is (Int) -> Int

No literal los valores de los tipos de función con y sin receptor son intercambiables, de modo que el receptor puede sustituir el primer parámetro y viceversa. Por ejemplo, un valor de tipo (A, B) -> C se puede pasar o asignar donde un A.(B) -> C se espera y al revés:

funmain()//sampleStartval repeatFun: String.(Int)-> String = times ->this.repeat(times)val twoParameters:(String, Int)-> String = repeatFun // OKfunrunTransformation(f:(String, Int)-> String): String returnf("hello",3)val result =runTransformation(repeatFun)// OK//sampleEndprintln("result = $result")

Tenga en cuenta que un tipo de función sin receptor se infiere de forma predeterminada, incluso si una variable se inicializa con una referencia a una función de extensión. Para modificar eso, especifique el tipo de variable explícitamente.

Invocar una instancia de tipo de función

Se puede invocar un valor de un tipo de función mediante su invoke(...) operador: f.invoke(x) o solo f(x).

Si el valor tiene un tipo de receptor, el objeto receptor debe pasarse como primer argumento. Otra forma de invocar un valor de un tipo de función con receptor es anteponerlo con el objeto receptor, como si el valor fuera una función de extensión: 1.foo(2),

Ejemplo:

funmain()//sampleStartval stringPlus:(String, String)-> String = String::plus
    val intPlus: Int.(Int)-> Int = Int::plus
    
    println(stringPlus.invoke("<-","->"))println(stringPlus("Hello, ","world!"))println(intPlus.invoke(1,1))println(intPlus(1,2))println(2.intPlus(3))// extension-like call//sampleEnd

Funciones en línea

A veces es beneficioso utilizar funciones en línea, que proporcionan un flujo de control flexible, para funciones de orden superior.

Expresiones lambda y funciones anónimas

Las expresiones lambda y las funciones anónimas son “literales de función”, es decir, funciones que no se declaran, pero que se pasan inmediatamente como una expresión. Considere el siguiente ejemplo:

max(strings, a, b -> a.length < b.length )

Función max es una función de orden superior, toma un valor de función como segundo argumento. Este segundo argumento es una expresión que es en sí misma una función, es decir, una función literal, que es equivalente a la siguiente función nombrada:

funcompare(a: String, b: String): Boolean = a.length < b.length

Sintaxis de expresión lambda

La forma sintáctica completa de las expresiones lambda es la siguiente:

val sum:(Int, Int)-> Int = x: Int, y: Int -> x + y 

Una expresión lambda siempre está rodeada por llaves, las declaraciones de parámetros en la forma sintáctica completa van entre llaves y tienen anotaciones de tipo opcionales, el cuerpo va después de una -> firmar. Si el tipo de retorno inferido de la lambda no es Unit, la última (o posiblemente única) expresión dentro del cuerpo lambda se trata como el valor de retorno.

Si dejamos fuera todas las anotaciones opcionales, lo que queda se ve así:

val sum = x: Int, y: Int -> x + y 

Pasando lambdas finales

En Kotlin, hay una convención: si el último parámetro de una función es una función, entonces una expresión lambda pasada como argumento correspondiente se puede colocar fuera del paréntesis:

val product = items.fold(1) acc, e -> acc * e 

Dicha sintaxis también se conoce como lambda final.

Si la lambda es el único argumento para esa llamada, los paréntesis se pueden omitir por completo:

run println("...")

it: nombre implícito de un solo parámetro

Es muy común que una expresión lambda tenga solo un parámetro.

Si el compilador puede descifrar la firma por sí mismo, se le permite no declarar el único parámetro y omitir ->. El parámetro se declarará implícitamente con el nombre it:

ints.filter it >0// this literal is of type '(it: Int) -> Boolean'

Devolver un valor de una expresión lambda

Podemos devolver explícitamente un valor de lambda utilizando la sintaxis de retorno calificada. De lo contrario, el valor de la última expresión se devuelve implícitamente.

Por lo tanto, los dos siguientes fragmentos son equivalentes:

ints.filterval shouldFilter = it >0 
    shouldFilter


ints.filterval shouldFilter = it >0return@filter shouldFilter

Esta convención, junto con pasar una expresión lambda fuera de paréntesis, permite Estilo LINQ código:

strings.filter it.length ==5.sortedBy it .map it.toUpperCase()

Subrayado para variables no utilizadas (desde 1.1)

Si el parámetro lambda no se utiliza, puede colocar un guión bajo en lugar de su nombre:

map.forEach _, value ->println("$value!")

Desestructuración en lambdas (desde 1.1)

La desestructuración en lambdas se describe como parte de las declaraciones de desestructuración.

Funciones anónimas

Una cosa que falta en la sintaxis de expresión lambda presentada anteriormente es la capacidad de especificar el tipo de retorno de la función. En la mayoría de los casos, esto es innecesario porque el tipo de retorno se puede inferir automáticamente. Sin embargo, si necesita especificarlo explícitamente, puede usar una sintaxis alternativa: an función anónima.

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

Una función anónima se parece mucho a una declaración de función regular, excepto que se omite su nombre. Su cuerpo puede ser una expresión (como se muestra arriba) o un bloque:

fun(x: Int, y: Int): Int return x + y

Los parámetros y el tipo de retorno se especifican de la misma manera que para las funciones normales, excepto que los tipos de parámetros se pueden omitir si se pueden inferir del contexto:

ints.filter(fun(item)= item >0)

La inferencia de tipo de retorno para funciones anónimas funciona igual que para funciones normales: el tipo de retorno se infiere automáticamente para funciones anónimas con un cuerpo de expresión y debe especificarse explícitamente (o se supone que es Unit) para funciones anónimas con cuerpo de bloque.

Tenga en cuenta que los parámetros de función anónimos siempre se pasan entre paréntesis. La sintaxis abreviada que permite dejar la función fuera del paréntesis funciona solo para expresiones lambda.

Otra diferencia entre las expresiones lambda y las funciones anónimas es el comportamiento de los retornos no locales. A regreso declaración sin una etiqueta siempre regresa de la función declarada con el divertida palabra clave. Esto significa que un regreso dentro de una expresión lambda volverá de la función adjunta, mientras que una regreso dentro de una función anónima volverá de la propia función anónima.

Cierres

Una expresión lambda o función anónima (así como una función local y una expresión de objeto) pueden acceder a su cierre, es decir, las variables declaradas en el ámbito externo. Las variables capturadas en el cierre se pueden modificar en la lambda:

var sum =0
ints.filter it >0.forEach
    sum += it
print(sum)

Literales de función con receptor

Tipos de funciones con receptor, como A.(B) -> C, se puede crear una instancia con una forma especial de literales de función: literales de función con receptor.

Como se dijo anteriormente, Kotlin proporciona la capacidad de llamar a una instancia de un tipo de función con el receptor que proporciona la objeto receptor.

Dentro del cuerpo de la función literal, el objeto receptor pasado a una llamada se convierte en un implícitoesta, para que pueda acceder a los miembros de ese objeto receptor sin ningún tipo de calificadores adicionales, o acceder al objeto receptor usando un this expresión.

Este comportamiento es similar a las funciones de extensión, que también le permiten acceder a los miembros del objeto receptor dentro del cuerpo de la función.

Aquí hay un ejemplo de una función literal con receptor junto con su tipo, donde plus se llama en el objeto receptor:

val sum: Int.(Int)-> Int = other ->plus(other)

La sintaxis de función anónima le permite especificar el tipo de receptor de una función literal directamente. Esto puede ser útil si necesita declarar una variable de un tipo de función con el receptor y usarla más tarde.

val sum =fun Int.(other: Int): Int =this+ other

Las expresiones lambda se pueden utilizar como literales de función con el receptor cuando el tipo de receptor se puede inferir del contexto. Uno de los ejemplos más importantes de su uso son los constructores con seguridad de tipos:

class HTML funbody()...funhtml(init: HTML.()-> Unit): HTML val html =HTML()// create the receiver object
    html.init()// pass the receiver object to the lambdareturn html


html // lambda with receiver begins herebody()// calling a method on the receiver object