En esta ocasión voy a intentar dar una “introducción” a lo que sería una forma distinta de ver la programación. Al igual que la recursion es simplemente otra manera de programar, no necesariamente más óptima, no es el objetivo, pero si un poco más acotada y clara (si se entiende, claro). La expresión Lambda.
ÍNDICE DE CONTENIDOS
INTRODUCCIÓN
En OTRO POST hablé de lo que son los delegados y para qué sirven. Es importante saber esto, puesto que los lambdas se basan mucho en estos tipos de datos.
En términos generales y sin muchas vueltas ni tecnicismos, son funciones anónimas que se pueden pasar por parámetro a otra función o usar para crear delegados. Son muy útiles para crear funciones que existen en el ámbito de un parámetro. O sea, crear la función, al momento de usarla nada más. Por ejemplo, podemos crear una función “Attack()” que reciba como parámetro un lambda que le diga al personaje “cómo atacar”. Podría tener un “Sim”, que tenga una función “SitDown()”, y por otro lado tener muebles que tengan lambdas que digan cómo sentarse o cómo usarlos. Entonces cada “Sim” podría utilizar en su función “SitDown()” o “UseObject()” dicho lambda para saber cómo tiene que sentarse. Esto hace que nuestro personaje tenga funcionalidades ampliamente escalables, ya que podría crear nuevos objetos todo el tiempo sin tener la necesidad de reprogramarlo. Lo único que tendría que hacer es que el nuevo objeto tenga una manera de decirle al personaje cómo usarlo.
Probablemente este sea una especie de “nuevo paradigma” para muchas personas, una especie de “reinvención” de la rueda. Pero si agarraron medianamente el ejemplo anterior, el resto es practicar más que nada, como cualquier cosa nueva que uno va a incorporar.
DEFINICIÓN
La declaración es de la siguiente manera:
() => {}
Lo sé… Lo vieron y…
Ok… Expliquemos esto de una manera que tenga un poco más de sentido. Empezaré comentando que la definición anterior lo que hace es crear una función QUE NO RECIBE NINGÚN PARÁMETRO y que además NO DEVUELVE NINGÚN VALOR. Y… Por sobre todo eso… NO HACE NADA, ya que entre las llaves no hay ninguna instrucción.
Si quisiéramos crear un lambda que reciba un parámetro, deberíamos ponerlo entre los paréntesis iniciales. O sea:
(string something) => {}
La expresión anterior crearía una función RECIBE UN STRING y NO DEVUELVE NADA. Además sigue sin hacer nada, puesto que no hay nada entre las llaves. Esto debería darles el pie para entender otra cosa: “Si quiero que haga algo, debería poner ALGO entre las llaves”. Ese ALGO es el código que quisiera ejecutar. Veamos otro ejemplo:
(string something) => { print("Imprimiendo: " + something;) }
Esta expresión lo que haría es crear una función que RECIBE UN STRING como parámetro, NO DEVUELVE NADA y muestra en pantalla el parámetro pasado. Veamos un uso un poco más útil todavía. Imaginemos que tenemos una lista de elementos que tienen una lista de palabras adentro:
List words = new List(); words.Add("Sarasa"); words.Add("Otra cosa"); words.Add("Esto es una palabra más larga"); words.Add("Algo"); words.Add("Inserte frase coherente acá..."); words.Add("Me quedé sin ideas"); words.Add("Tu tu tururururutu turu tu tu tururururu tu");
En este ejemplo, la lista tiene 7 elementos que serían frases. Imaginemos que querémos obtener el elemento que tenga 4 caracteres. Las listas tienen una función llamada “Find” que recibe como parámetro una función que diga cuál es el parámetro de búsqueda. La función que le pasemos por parámetro debería recibir como parámetro un “string” (esto es porque la lista tiene strings adentro), y devolver un valor booleano. Si el valor booleano es true quiere decir que encontramos el objeto que estamos buscando, sino, la función “Find” seguirá buscando. Vamos a ver dos formas de hacer esto.
La primera, haciendo de cuenta que los lambdas no existen:
void Start() { List words = new List(); words.Add("Sarasa"); words.Add("Otra cosa"); words.Add("Esto es una palabra más larga"); words.Add("Algo"); words.Add("Inserte frase coherente acá..."); words.Add("Me quedé sin ideas"); words.Add("Tu tu tururururutu turu tu tu tururururu tu"); string wordWithFourLetter = words.Find( PredicateFind ); } bool PredicateFind(string text) { if(text.Length == 4) { return true; }else { return false; } }
Con el uso de lambdas, se resumiría a lo siguiente:
void Start() { List words = new List(); words.Add("Sarasa"); words.Add("Otra cosa"); words.Add("Esto es una palabra más larga"); words.Add("Algo"); words.Add("Inserte frase coherente acá..."); words.Add("Me quedé sin ideas"); words.Add("Tu tu tururururutu turu tu tu tururururu tu"); string wordWithFourLetter = words.Find( (string text) => {return text.Length == 4;} ); }
Lo que haría C# acá es crear la función directamente al momento de necesitarla. Incluso se podría resumir un poco más. ¡Presten atención al cambio!
void Start() { List words = new List(); words.Add("Sarasa"); words.Add("Otra cosa"); words.Add("Esto es una palabra más larga"); words.Add("Algo"); words.Add("Inserte frase coherente acá..."); words.Add("Me quedé sin ideas"); words.Add("Tu tu tururururutu turu tu tu tururururu tu"); string wordWithFourLetter = words.Find( text => text.Length == 4 ); }
¿Cuál es la explicación de esto? Bueno… C# es lo bastante “inteligente” como para darse cuenta que “text” es un parámetro de tipo string y que la función devuelve un valor booleano. ¿Cómo sabe esto? Fácil… Porque C# sabe que la función “Find()” pide como parámetro una función que reciba como parámetro EL MISMO TIPO DE DATO que guarda la lista y que devuelva un valor booleano. Entonces supone que la variable “text“, que es la que escribimos ahí, es del tipo de dato string, y supone también que la expresión que pusimos a continuación va a ser lo que querémos devolver, por ende ejecuta el return automáticamente.
A simple vista, parece más complicado de lo que en realidad es. Pero denle una lectura nuevamente. ¡HAGAN UN ESFUERZO!
USOS
En el desarrollo de videojuegos hay varios casos para los que se podría usar esto. Por ejemplo, podría decirle a mi enemigo que ataque, y pasarle por parámetro con qué criterio este debería hacerlo. Veamos el código. Imaginemos que la función “Attack()” debe seleccionar una skill para usar entre 0 y 4, donde 0 es “no atacar”.
Character.cs
public void Attack(Func<Character, int> predicate) { int skillID = predicate(this); CastSkill(skillID); }
En otro script cualquiera… Podríamos ejecutar la función “Attack()” de la siguiente manera:
public void Update() { if(canAttack) { //Esto lo que haría es usar la skill 1 si el personaje tiene más de 10 de vida. Sino, usa la 2. character.Attack( (Character c) => { return c.life > 10 ? 1 : 2 } ); //Esto también se puede simplificar de esta manera, puesto que C# detecta automáticamente que "c" //es del tipo de dato "Character" y que la sentencia después de "=>" devuelve un "int". character.Attack( c => c.life > 10 ? 1 : 2 ); //Incluso si quisiera que use una skill determinada, como por ejemplo la 4, podría escribir lo siguiente: character.Attack( c => 4 ); } }
Muchos usos de los lambdas están dentro de las listas, arrays, diccionarios, etc. O sea, cualquier tipo de dato que implemente la interfaz “IEnumerable”. Incluso nuestros scripts podrían usarlos con sólo implementar dicha interfaz. Lo importante de esto es que incluso las funciones que se agregan como “Where”, “SkipWhile”, “TakeWhile”, “OrderBy”, etc, devuelven objetos de tipo “IEnumerable”. Esto significa que… Se puede hacer un “Where”, después de otro, y otro, y otro. Los usos se extienden a la imaginación de cada uno.
EL TIPO “IEnumerable”
Este tipo de dato es una interfaz. Lo que quiere decir es que es un tipo de dato que obliga a otro a contener determinadas funciones. Todos los scripts que implementen esta interfaz pueden ser enumerados, lo que significa que pueden usarse no sólo con LINQ sino que también pueden ser recorridos con un foreach.
Esto es MUY lo básico de Lambdas. Les quedaría practicarlo y dejar que sus mentes vuelen con eso. Si lo aprenden a usar bien, se van a dar cuenta la importancia de su uso, y lo fácil que se les va a hacer la vida después.
Espero les sirva.
UPDATE
Agrego esta imagen que explica un poco más algunas funciones de LINQ:
