lunes, 20 de junio de 2011

Delegados y Expresiones Lambda

El otro día en la oficina Pedro y yo estuvimos hablando sobre la mejor forma para escribir delegados en .NET. Cuando digo la mejor forma quiero decir la manera más elegante y legible, que facilite en la manera de lo posible el mantenimiento del código por otra persona.
Antes de nada, si alguien no sabe lo que es un delegado, se podría decir que es un puntero a función. Utilizando un lenguaje lo menos académico posible, un delegado es un mecanismo que nos permite pasar un método como parámetro o asignarlo a una variable. Por ejemplo:
delegate void TextProcessor(string text);

static void ConsoleWrite(string text)
{
 Console.WriteLine(text);
}

static void Main(string[] args)
{
 TextProcessor exampleDelegate = new TextProcessor (ConsoleWrite);
 exampleDelegate ("Hola Mundo");
}
En el ejemplo tenemos una variable exampleDelegate que apunta al método ConsoleWrite y que puede invocarlo.
Lo cierto es que con el paso del tiempo, .NET ha ido evolucionando y permite una forma mucho más sencilla escribir un delegado:
static void Main(string[] args)
{
 Action <string> exampleDelegate = delegate (string text) {Console.WriteLine (text);};
 exampleDelegate ("Hola Mundo");
}
Podemos ver que hemos reducido claramente el código, utilizamos un método anónimo para definir el método de delegado en línea y utilizamos Action <string> para indicar que el delegado que se va a definir devuelve void y recibe un único parámetro de tipo string.
¿Y que es una expresión Lambda en .Net? Con el framework 3.0 aparecieron las expresiones Lambda en .NET que se usan para crear delegados con otra sintaxis:
static void Main(string[] args)
{
 Action <string> exampleDelegate = (text)=>{Console.WriteLine(text);};
 exampleDelegate ("Hola Mundo");
} 
Básicamente una expresión lambda se compone de dos partes los paréntesis donde se escriben los parámetros del delegado y el cuerpo del método que va entre llaves. Estas dos partes se separan por =>
Voy a poner otro ejemplo, también sencillo, pero esta vez, con un delegado que devuelva valores:
static void Main(string[] args)
{
 Func <int, int, int> exampleDelegate = delegate (int x, int y){return x * y;};
 Func<int, int, int> exampleLambda = (x, y) => { x * y; };

 Console.WriteLine("Resultado de exampleDelegate {0}", exampleDelegate (5, 3));
 Console.WriteLine ("Resultado de exampleLambda {0}", exampleLambda (3, 5));
}
La única diferencia destacable con los ejemplos anteriores es que este, al devolver un valor usamos Func, a la que le pasamos primero los tipos de los parámetros y por último el tipo que devuelve. En la expresión lambda no es necesario escribir return, se devuelve el resultado de la última (en este caso única) línea.
Los que participamos en este blog, Pedro y yo, tenemos opiniones diferentes a la hora de escribir los delegados, e intentaré ser lo más imparcial a la hora de exponer las opiniones de los dos.
Pedro prefiere no usar las expresiones lambda, porque (y tiene razón) a la hora de ver el código podemos ver los tipos que se usan y también aparece la palabra delegate, lo que permitiría a un programador buscarlo en google, mientras que “=>” no sale en las búsquedas.
Por otro lado, a mi me gustan más las lambda. Creo que genera código más limpio, más legible y elegante.
Vaaale, parece que Pedro tiene mejores argumentos que yo, pero como es mi post, voy a poner algún ejemplo chorras con lambda y listas genéricas para que veáis como quedan:
public class Developer
{
 public string Name {get; set;}
 public string Technology {get; set;}
}

static void Main(string[] args)
{
 List<Developer> developers = new List<Developer> {
     new Developer {Name = "Pedro", Technology = "Java"},
     new Developer {Name = "Manel", Technology = ".NET"},
     new Developer {Name = "Oscar", Technology = ".NET"},
     new Developer {Name = "Noe", Technology = "Java"},
     new Developer {Name = "Elias", Technology = "Java"}
 };

 List<Developer> coolProgrammers = developers.Where(x =>".NET".Equals(x.Technology)).ToList();
 List<Developer> bloggers = developers.Where(x => ("Pedro".Equals(x.Name)
                                                    || "Manel".Equals(x.Name))).ToList();

 bloggers.OrderBy(x => x.Name).ToList().ForEach((x) => { Console.WriteLine(x.Name); });
}
¿Qué opináis? Si tuvieseis que mantener un código no escrito por vosotros ¿qué preferiríais? ¿lambdas o delegados “tradicionales”?

10 comentarios:

  1. No sé si le pasará a más gente. Personalmente, se me hace raro ver lo que parece Caml en medio de código no funcional, aunque sea una forma cómoda de definir funciones.
    En cualquier caso, ambas formas me parecen igualmente comprensibles y, por lo tanto, mantenibles. Si te encuentras con sintaxis desconocida a la hora de mantener código, quizá deberías hacerte con un manual en lugar de atacar a Google. Cuando ya sabes el nombre de esa construcción, el hecho de que "=>" no salga en Google se convierte en irrelevante, porque puedes buscar por "lambda expressions .net".

    ResponderEliminar
  2. Yo soy de la opinión de que las APIs, cuanto más claritas y más tipadas, mejor. Cuando un programador no entiende lo que hace un código y lo tiene que mantener, tiende a duplicar el código manteniendo el ininteligible y reescribiéndolo en algo que pueda comprender.

    Así que hay que limitar en la medida de lo posible la creatividad de los programadores. Y formarlos si crees que vas a usar algo que ellos no conocen.

    Si, lo sé, deberían conocerlos, pero es la calidad media que tenemos en la empresa privada.

    ResponderEliminar
  3. Estoy de acuerdo, sobre todo con tu primera afirmación, por eso me resulta extraño mezclar constructos sintácticos heredados del mundo funcional en medio de código imperativo.

    De todas formas, siguiendo con el planteamiento de limitar la creatividad de los programadores, a día de hoy no usaríamos genéricos en Java, por ejemplo.

    Además, en el ejemplo se usan las expresiones lambda para incrustar funcionalidad en el código sin declarar antes un objeto que la contenga. En ese sentido (y sólo en ese sentido) se parece al uso de clases anónimas incrustadas en el código en Java, que daña incluso más la legibilidad.

    Resumiendo, que voy para largo, mientras el uso de una característica sea correcto y adecuado a su propósito, no veo razones para limitar su uso sólo por la posibilidad de que un futuro programador no pueda seguir el código. Por ese camino nos quedaríamos hasta sin operador ternario ;)

    ResponderEliminar
  4. A mi, personalmente me gusta más (como pongo en el post) con lambdas. ¿por qué? por la limpieza y economía en el codigo (cuanto menos escribas menos posibilidades de meter la pata).

    ¿Qué puede resultar confuso para alguien la sintaxis? puede de ser. ¿Debe un programador dejar de usar algo comodo porque alguien no tenga los conocimientos técnicos para saber lo que es?
    Por ese motivo (si lo llevamos al absurdo), deberíamos de dejar de usar la herencia y poner unos gigantes if elses en cada método. Ya que hay muchos programadores que se sienten más cómodos con un único método de muchas líneas que tener el codigo desperdigado en clases y métodos. ¿no?

    ResponderEliminar
  5. Creo que la innovación hay que usarla sin pasarse. A día de hoy no me plantearía un proyecto Java sin generics. Pero bueno, aún hoy por hoy, el nivel de comprensión de la gente no es completo.

    ¿Cuántos programadores conocéis que sepan hacer un método cuyo tipo de retorno sea función del tipo de alguno de los parámetros, y que no sea uno de los tipos genéricos que se le pasan a la clase?

    Cuanto más grande sea el proyecto y/o mayor equipo haya, o sin experiencia, menores florituras les exigiría (porque hay de todo).

    Eso si, si puedo, innovo. Por mi, y por los programadores que pueden dar el nivel.

    En el caso de las expresiones lambda, pues no lo sé, a mi parecer es una vuelta de tuerca sobre los delegates, que ya no son muy entendidos por el programador medio .net .

    Pero bueno, una anécdota (que funcionaba) adaptada :

    public class Test {
     public static void main(String args[]) {
      List list = new ArrayList<Integer>();
      list.add("blablabla");
      funcion(list);
     }
     public static void funcion(List array) {
      String str = (String) (array.get(0));
      System.out.println(str);
     }
    }

    ResponderEliminar
  6. A expensas de que una persona se pierda si no conoce la tecnología (aunque no debe ser muy difícil encontrar información al respecto, ¿o me equivoco?), me decanto por las expresiones lambda. No soy amiga de las sentencias complejas de una sola línea, pero por los ejemplos que mostrais en el post, me resulta incluso más simple y legible.

    ResponderEliminar
  7. Noe, me encantaría ver cómo programadores creativos que-tú-ya-sabes escriben bucles que llaman a delegados. Usando el código de Manel, imagínate que la última línea es ...

    foreach (Developer d in bloggers.OrderBy(x => x.Name).ToList())
    {
    Console.WriteLine(d.Name);
    }

    ... ¡¡¡ Viva la Pepa !!! Ala, a mezclar paradigmas ...

    ResponderEliminar
  8. No sé .NET, pero por curiosidad (de esa que no sirve para nada) ¿se puede hacer algo como esto?:

    Action exampleDelegate = Console.WriteLine ;

    Supongo que no, pero por preguntar no pierdo nada ;)

    ResponderEliminar
  9. ¿No se puede:
    Action exampleDelegate = Console.WriteLine;
    ?
    [troll on]
    Quedaría todavía más resumido y claro si no se necesita rellenar parte de los parámetros ;)
    [troll off]

    ResponderEliminar
  10. Como necesita un parametro sería
    Action exampleDelegate = Console.WriteLine;
    Pero sí, tu idea es correcta.

    ResponderEliminar