Una de las funcionalidades más potentes que tiene Kotlin y que en su momento lo diferenci�
bastante de Java, aunque poco a poco se ha ido poniendo las filas con este tema, son
las expresiones lambda. Las expresiones lambda nos permiten definir el código de una función sin necesidad de
tener que definir la función de forma completa, como lo hemos visto con la palabra reservada
fun, las llaves, los argumentos, el return... Al final una función no es más que una expresión con valores de entrada y un valor de salida
y en muchas ocasiones el nombre que le damos a la función tampoco aporta demasiado
Por tanto, las lambdas son una forma más sencilla de definir funciones. Además, en el caso de Kotlin, el propio sistema de tipos nos permite definir una lambda como
un tipo. ¿Cómo es esto? Pues vamos a verlo. Imagínate que tienes una función sum que recibe dos valores, x e y, y que devuelve x
más y. Si te fijas, al final esto es una función que recibe dos valores y devuelve un valor
El sistema de tipos, igual que nos permite definir una variable de tipo entero, también
nos permite definir una variable que almacene un código que reciba dos enteros y devuelva
un entero. ¿Cómo se haría esto? Representando esta función como un tipo, tendríamos una flecha que identifica en el
lado izquierdo el tipo de los valores de entrada y en el lado derecho el tipo de los valores
de salida. En el lado izquierdo siempre tenemos que poner paréntesis que equivaldrían a estos paréntesis
de aquí. Si una lambda no recibe ningún valor de entrada, se quedan los paréntesis vacíos y si no
devuelve ningún valor de salida, devuelve por defecto el tipo unit. Ya veíamos cuando hablábamos del sistema de tipos que en Kotlin cuando no se devuelve
nada realmente se está devolviendo el tipo unit. Pero este no es el caso de esta función
Esta función está recibiendo dos valores de entrada. En nuestro caso sería un entero y un entero
Y de valor de salida, aunque aquí no lo hayamos definido porque hemos definido la función como una expresión, la expresión devuelve un entero
Por tanto, este unit no es un unit, sino que es un entero. Con este tipo yo me puedo ir a una variable, definir que esa variable va a ser de este
tipo y en el lado derecho puedo definir cuál es la implementación de este tipo
La implementación de este tipo tiene que ser una función, una lambda, que reciba dos
valores enteros y devuelva un valor entero. ¿Cómo defino una lambda? Abro llaves, nuevamente utilizo una flecha para diferenciar los dos lados de la lambda
en el lado izquierdo identifico los argumentos de entrada, en este caso son x e y, y en el
lado derecho el cuerpo de la función, es decir, la expresión que va a utilizar estos
valores y va a devolver un valor, en nuestro caso x más i. A esto en vez de x lo voy a llamar sum porque es la función suma o la lambda suma y ahora
yo podría ir a cualquier sitio, por ejemplo a un println voy a comentar esta función
porque como se llaman igual, si no va a haber conflictos, voy a llamar a sum de 2 y de 3
y esto si lo ejecuto nos dará un 5. Esta es una forma de definir las lambdas, pero ya hemos dicho varias veces que Kotlin
es capaz de inferir los tipos si le damos la información necesaria
Entonces en vez de escribir aquí todo el tipo, lo que podemos hacer es identificar
de qué tipo son los argumentos de entrada directamente en la definición de la lambda
a partir de eso y de la expresión será capaz de calcular el tipo completo
Por ejemplo, va el mul que recibe dos enteros y los multiplica, yo en vez de poner esta
parte de aquí podría simplemente quitar todo esto, poner igual y dado un x y un y
hacer x por y y hacer mul de 2 por 3. Esto no va a funcionar, ¿por qué
Porque aquí no es capaz de inferir de qué tipo son estos parámetros, pero se lo puedo
decir, le digo que esto es de tipo entero y esto es de tipo entero y automáticamente
sabe que x por y es una expresión que devuelve un valor entero a partir de dos enteros y
por tanto sabe que es de este tipo. No se lo tengo que especificar aquí explícitamente
Si yo ejecuto esto, veremos que 2 por 3 nos da 6. Imagínate que yo ahora tengo una función que realiza una operación, recibe un valor
de tipo entero, otro de tipo entero y una operación que dados dos enteros devuelve
otro entero y lo único que vamos a hacer, vamos a escribirlo como una expresión, es
igual a op de x y de y, es decir, esta es una función genérica que a dos valores les
aplica una operación que le pasamos como argumento. ¿Qué podríamos hacer aquí
Pues por ejemplo un do op de 2, 3 y le pasamos la función suma
La función suma tiene esta estructura exactamente, que recibe dos enteros y devuelve un entero
Si tuviéramos otra operación que no tuviera exactamente esta estructura, no podríamos
pasársela como argumento. Ahora si quieres vemos un ejemplo. Entonces ejecutamos esto y ahora 2, 3 aplicará la suma, entonces devuelve un 5
Y aquí en vez de aplicar la suma, aplicamos la multiplicación, nos dará 6
2 por 3, 6. Pero incluso podemos definir la lambda aquí de forma inline
Inline quiere decir en el punto en el que la vamos a utilizar. Y aquí podemos hacer que dado un x y un y haga por ejemplo x menos y
Si te fijas esto lo puedo ejecutar y aquí nos va a hacer 2 menos 3, que es la operación
que estamos haciendo en este punto. ¿Por qué aquí no le tengo que identificar que esto es un entero y que esto es otro
Porque ya lo da por hecho, porque al usarlo en el punto donde se espera una función que
tiene dos enteros y devuelve un entero, ya automáticamente infiere que estos dos valores
van a ser enteros. Es decir que en cuando el compilador tiene la información suficiente no necesitamos
dársela explícitamente. Una cosa interesante que tienen las lambdas es que cuando la lambda es el último argumento
de otra función, en este caso esta función tiene tres argumentos y la lambda es el último
argumento, lo que podemos hacer es sacarlo de los paréntesis. Cerramos paréntesis aquí, abrimos llaves y la lambda la definimos desde fuera
Ejecutamos, esto va a dar exactamente lo mismo. Pero ¿por qué es esto interesante? Porque entonces nos podemos crear como una especie de bloques de código
Lo voy a sacar a una variable, por ejemplo va al res y aquí le vamos a pasar el res
Imagínate que esta operación no fuera tan sencilla como hacer simplemente x menos y
Podríamos poner esto en varias líneas y añadir aquí tanto código como necesitemos
Y esto nos genera bloques de código que es muy interesante para crear DSLs personalizados
DSL son las siglas de Domain System Language y nos permite generar nuestro sistema de lenguaje
de forma totalmente personalizada. Por ejemplo, si te vas a la web de Kolding, verás que tienen un ejemplo en cómo generar
un DSL que te permita generar HTML a partir de funciones que tienen los mismos nombres
que los elementos del HTML. Por ejemplo, para crear un párrafo, para crear un header, este tipo de cosas
Y esto se puede extrapolar a un montón de cosas. Lo verás muy a menudo utilizado en distintas funcionalidades de Kolding
El caso es que te quedes con eso. Si el último argumento de una función es a su vez una función o una lambda, entonces
lo puedes extraer de esta forma. Por otro lado, si esta función solo tuviera este argumento, el de op, en este caso no
tiene mucho sentido, se quedaría algo así y en estos casos puedes incluso quitarte los paréntesis
Y de esa forma genera bloques de código muy interesantes que ya te digo que dan una versatilidad
enorme al lenguaje. Una cosa que no te he comentado es que si una lambda tiene varias líneas, imagínate
que hiciéramos otra cosa. El valor de retorno de la lambda es el valor de la última expresión
Aquí no hay que poner return porque este return se referiría a la función main, no
a esta lambda. La función de la expresión de la última línea de la lambda es la que va a devolver
el valor que finalmente se va a poder utilizar desde fuera. En este caso, imagínate que esa otra cosa fuera 3x4
Esto realmente no estaría haciendo nada. El valor que se devuelve es el de x-y
Fíjate que las lambdas son muy versátiles y que nos permiten muchas ventajas que en
otros lenguajes no tenemos disponibles. Por un lado, que las propias lambdas forman parte del sistema de tipos, por tanto nuestras
funciones pueden recibir lambdas, devolver lambdas y esto da muchas capacidades funcionales
al lenguaje y además al poder extraerla de las paréntesis cuando llamamos a la función
hace que podamos generar DSLs propios que nos den todavía más versatilidad al lenguaje
Otra cosa que no te he dicho es que puedes utilizar las funciones como si fueran lambdas
siempre y cuando cumplan con la estructura de la función o lambda que se espera
Imagínate que lo que queremos hacer aquí es simplemente llamar a esta función sum
que tenemos aquí abajo. Fíjate que cuando estuvimos generando el tipo que correspondía con esta función es
exactamente el tipo que está esperando esta operación. Vamos a añadir aquí x de tipo int e y de tipo int
Pues podríamos pasarle por aquí a la función duope los valores que queremos aplicar, por
ejemplo, 2 y 3 y como tercer argumento le podríamos pasar esta función
Pero esto es una función, no es una variable como teníamos aquí arriba que la llamábamos
muy fácilmente poniendo aquí sum. Lo que tenemos que hacer para llamar a una función y que se comporte como una lambda
es llamar a la referencia de la función y para llamar a la referencia de una función
tenemos que poner dos dobles puntos por delante. De esta forma le decimos no quiero llamar a la función sino que lo que quiero es pasar
esa función como argumento. Si ahora ejecuto esto tendremos el mismo resultado
Esta función se está comportando como una lambda ya que la estructura de esta función
es la misma que la de la lambda que se espera. Recibe dos parámetros de entrada de tipo entero y devuelve un entero
Así que con esto ya hemos cubierto cómo funcionan las lambdas