miércoles, 30 de octubre de 2019

Paradigmas y tipos de lenguajes informáticos (3 de 3). Programación declarativa en lenguajes empresariales


Aunque casi todos los lenguajes empresariales se presentaban como lenguajes multiparadigma, lo cierto es que casi siempre eran lenguajes que admitían programación imperativa (con alguna de sus evoluciones) junto a programación orientada a objetos, sin acercase a algún otro tipo de paradigma. Esto cambio en los últimos años donde se incorpora, en los lenguajes de programación funciones y sintaxis declarativas que facilitan el desarrollo de software.


La programación declarativa se basa en crear código que expresa las necesidades que queremos resolver y cómo resolverlas (mas información en nuestra entrada anterior), a diferencia de la programación imperativa que se basa en seguir diversos pasos en los que indicamos como resolver un problema. Es por lo tanto mucho mas practico (y sencillo) un código declarativo (en que queda claro el problema), que uno imperativo para el tenemos que hacer un análisis más profundo para comprenderlo.


Como curiosidad, la programación declarativa es anterior a la programación imperativa, entonces ¿Por qué se impuso la programación imperativa, sobre la declarativa entonces? Fueron motivos comerciales más que nada, la programación declarativa tenía un deje mas científico que practico (de origen), y la programación imperativa se ajustaba mas a la comprensión del cómo funcionaba una maquina (considerando sus capacidades de procesamiento y su velocidad). Parecía más sencillo de comprender programas que indicaban una instrucción tras otra. A esto se debe añadir lo difícil de construir compiladores, ya que no se poseían las herramientas (ni las metodologías de análisis), actuales. En un mercado emergente como la computación, que se comenzaba a incorporar a las empresas, perecía más razonable diseñar lenguajes imperativos que sirvieran para vender computadoras, que lenguajes declarativos.


Un ejemplo de lo anterior es COBOL, que tiene un origen muy peculiar. Es uno de los lenguajes más antiguos existentes (del 1959). En su momento empresas como IBM estaban viendo la viabilidad de vender comercialmente y de forma masiva sus computadoras a negocios que necesitaban grandes herramientas de procesamiento de información, tal como bancos. El problema es que esas maquinas, eran monstruos enormes y terriblemente caras, y los directores de los bancos que autorizaban dichas compras no se sentían felices pagando dichas millonadas, para algo que difícilmente entendía (y para colmo tampoco entendían a los primeros informativos y matemáticos que si las podían manejar). La solución fue COBOL, un lenguaje que “parecía”, que era como escribir inglés, y con el que directores bancarios creían que podían estar más cerca de cómo funcionaban dichas maquinas, sintiéndose mas cómodos al desembolsar lo que constaba una computadora de la época (evidentemente todo era un efecto placebo, independiente de la potencia y capacidades de COBOL, solo querían saber que pudieran comprenderlo, aunque evidentemente no lo hacían). Esto es como un ejemplo de cómo un lenguaje imperativo ayudo a la venta comercial de computadoras.


En lo personal creo que todavía en la actualidad es inviable crear un sistema empresarial usando completamente un paradigma declarativo, pero realmente algunas partes es mejor crearlas de forma declarativa por su sencillez y claridad, es aquí donde se pueden mezclar la programación imperativa, la programación orientada a objetos, y la programación declarativa, para usar lo mejor de cada uno, según la necesidad.

A continuación algunos ejemplos de programación declarativa en lenguajes con enfoque empresarial.

Ruby


Ruby es posiblemente unos de mis lenguajes preferidos, tuvo un auge muy importante a principios de siglo, aunque en esta década (a partir de 2010) comenzó a decaer, frente a su principal competidor Python.


Ruby es un lenguaje multiparadigma, con una inspiración declarativa muy fuerte, como vemos en el siguiente comentario de su creador Yukihiro "Matz" Matsumoto:

A menudo la gente, especialmente los ingenieros en computación, se centran en las máquinas. Ellos piensan, "Haciendo esto, la máquina funcionará más rápido. Haciendo esto, la máquina funcionará de manera más eficiente. Haciendo esto..." Están centrados en las máquinas, pero en realidad necesitamos centrarnos en las personas, en cómo hacen programas o cómo manejan las aplicaciones en los ordenadores. Nosotros somos los jefes. Ellos son los esclavos.

Características declarativas de Ruby


Facilidad para expresar nuestras necesidades.


Ruby tiene muchas facilidades para expresar lo que necesitamos de una forma clara y con pocas líneas de código:

  • Declaración de variables

    En Ruby siempre es necesario asignar un valor a una variable, al momento de declararla. El tipo no se indica explícitamente, si no que se toma del valor de la variable.

    Asignación Ruby
     
    1
    2
    3
    4
    5
    6
    # *****************************************************
    # En  Ruby siempre es necesario asignar un valor a una variable, al momento de declararla.
    # El tipo no se indica explícitamente, si no que se toma del valor de la variable.
    # *****************************************************
     
    mensaje = 'Hola Mundo!!!'
     
     
    # *****************************************************
    # En  Ruby siempre es necesario asignar un valor a una variable, al momento de declararla.
    # El tipo no se indica explícitamente, si no que se toma del valor de la variable.
    # *****************************************************
    
    mensaje = 'Hola Mundo!!!'
    

  • Para declara un array simplemente se lo asignamos a la variable.

    Declaración Array
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Para declara un array simplemente se lo asignamos a la variable.
    # *****************************************************
     
    mensajes = ['Hola Juan', 'Hola Maria', 'Hola Pedro']
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro"]
     
     
    # *****************************************************
    # Para declara un array simplemente se lo asignamos a la variable.
    # *****************************************************
    
    mensajes = ['Hola Juan', 'Hola Maria', 'Hola Pedro']
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro"]
    

  • Para declarar un Dictionary (tambien llamdo Hash o Map, solo declaramos los valores y las llaves:

    Declaración de Hash
     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    # Hash vacio
     
    hash = {}
     
    # Hash con valores
    hash =  {
                saludo_juan: 'Hola juan',
                saludo_maria: 'Hola Maria',
                saludo_pedro: 'Hola pedro',
            }
             
    puts hash.inspect
    # Salida: {:saludo_juan=>"Hola juan", :saludo_maria=>"Hola Maria", :saludo_pedro=>"Hola p
     
     
    # Hash vacio
    
    hash = {}
    
    # Hash con valores
    hash =  { 
                saludo_juan: 'Hola juan',
                saludo_maria: 'Hola Maria',
                saludo_pedro: 'Hola pedro',
            }
            
    puts hash.inspect
    # Salida: {:saludo_juan=>"Hola juan", :saludo_maria=>"Hola Maria", :saludo_pedro=>"Hola p
    

    Salida: ["Hola Juan", "Hola Maria", "Hola Pedro"]

  • Agregar un elemento a un array

    Agregar un elemento a un array
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Agregar un elemento a un array
    # *****************************************************
     
    mensajes << 'Hola Jose'
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose"]
     
     
    # *****************************************************
    # Agregar un elemento a un array
    # *****************************************************
    
    mensajes << 'Hola Jose'
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose"]
    

    Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose"]

  • Agrego un elemento en una posición aleatoria

    Agrego un elemento en una posición aleatoria
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Agrego un elemento en una posición aleatoria
    # *****************************************************
     
    mensajes[6] = 'Hola Elias'
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias"]
     
     
    # *****************************************************
    # Agrego un elemento en una posición aleatoria
    # *****************************************************
    
    mensajes[6] = 'Hola Elias'
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias"]
    

    Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias"]

  • Agrego un elemento al final

    Agrego un elemento al final
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Agrego un elemento al final
    # *****************************************************
     
    mensajes << 'Hola Gustavo'
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias", "Hola Gustavo"]
     
     
    # *****************************************************
    # Agrego un elemento al final
    # *****************************************************
    
    mensajes << 'Hola Gustavo'
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias", "Hola Gustavo"]
    

    Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias", "Hola Gustavo"]

  • Agrego un elemento al principio

    Agrego un elemento al principio
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Agrego un elemento al principio
    # *****************************************************
     
    mensajes.unshift('Hola Roberto')
     
    puts mensajes.inspect
    # Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias", "Hola Gustavo"]
     
     
    # *****************************************************
    # Agrego un elemento al principio
    # *****************************************************
    
    mensajes.unshift('Hola Roberto')
    
    puts mensajes.inspect
    # Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias", "Hola Gustavo"]
    

    Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", nil, nil, "Hola Elias", "Hola Gustavo"]

  • Buscar elementos

    Buscar un elemento, aquellos mensajes que acaben con la letra o, aquí se usan expresiones lamba, las veremos después

    Buscar un elemento
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # *****************************************************
    # Buscar un elemento
    # Busco aquellos mensajes que acaben con la letra o, aquí se usan expresiones lamba, las veremos después
    # *****************************************************
     
    encontrados=mensajes.select {|e| e=~/o$/}
     
    puts encontrados.inspect
    # Salida: ["Hola Roberto", "Hola Pedro", "Hola Gustavo"]
     
     
    # *****************************************************
    # Buscar un elemento
    # Busco aquellos mensajes que acaben con la letra o, aquí se usan expresiones lamba, las veremos después
    # *****************************************************
    
    encontrados=mensajes.select {|e| e=~/o$/}
    
    puts encontrados.inspect
    # Salida: ["Hola Roberto", "Hola Pedro", "Hola Gustavo"]
    

    Salida: ["Hola Roberto", "Hola Pedro", "Hola Gustavo"]

  • Eliminar elementos de un array (por ejemplo los nulos)

    Eliminar elementos de un array
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Eliminar elementos de un array (por ejemplo los nulos)
    # *****************************************************
     
    mensajes.delete_if {|e| !e }
     
    puts mensajes.inspect
    # Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo"]
     
     
    # *****************************************************
    # Eliminar elementos de un array (por ejemplo los nulos)
    # *****************************************************
    
    mensajes.delete_if {|e| !e } 
    
    puts mensajes.inspect
    # Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo"]
    

    Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo"]

  • Trabajar con un array como una cola (último en entrar último en salir)

    Colas
     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    # *****************************************************
    # Trabajar con un array como una cola (ultimo en entrar ultimo en salir)
    # *****************************************************
     
    mensajes << 'Hola Edgar'
    mensajes << 'Hola Luz'
     
    puts mensajes.inspect
    # Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]
     
    mensajes.shift()
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]
     
     
    # *****************************************************
    # Trabajar con un array como una cola (ultimo en entrar ultimo en salir)
    # *****************************************************
    
    mensajes << 'Hola Edgar'
    mensajes << 'Hola Luz'
    
    puts mensajes.inspect
    # Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]
    
    mensajes.shift()
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]
    

    Salida: ["Hola Roberto", "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]

    Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]

  • Trabajar con un array como una pila (ultimo en entrar, primero en salir)

    Colas
     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    # *****************************************************
    #Trabajar con un array como una pila (ultimo en entrar, primero en salir)
    # *****************************************************
     
    mensajes.push('Hola Ruben')
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz", "Hola Ruben"]
     
    mensajes.pop()
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]
     
     
    # *****************************************************
    #Trabajar con un array como una pila (ultimo en entrar, primero en salir)
    # *****************************************************
    
    mensajes.push('Hola Ruben')
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz", "Hola Ruben"]
    
    mensajes.pop()
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]
    

    Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz", "Hola Ruben"]

    Salida: ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar", "Hola Luz"]

Como vemos todo el tratamiento de los arrays es bastante declarativo, parecido a PROLOG y LISP, en ningún caso debemos preocuparnos por cómo se almacenan los elementos del array, ni en tareas tales como reservar memoria o liberarla. Tampoco debemos conocer estructuras específicas para cada tarea propias de la programación orientada a objetos como Listas, Pilas, o Colas.

Bloques que contiene código


Ruby da una mezcla muy buena entre programación orientada a objetos y programación declarativa. Básicamente casi todo está orientado a objetos, tanto es así que no existen estructuras como los bucles for para recorrer colecciones, existe una método each (de la colección) y recibe como parámetro un función que será ejecutada por cada elemento de la colección.

La función que ejecutar el método recibe el nombre de bloque y puede ser especificada de una forma declarativa entre llaves o entre las clausulas do/ end .

  • Bloque especificado por llaves

    Imprimir el cuadro de cada numero
     
    1
    2
    3
    4
    5
    6
    7
    #Imprimir el cuadro de cada numero
     
    [1,2,3].each { |x| puts x*2
    # Salida:
    # 2
    # 4
    # 6
     
     
    #Imprimir el cuadro de cada numero
    
    [1,2,3].each { |x| puts x*2 }  
    # Salida: 
    # 2
    # 4
    # 6
    

  • Bloque do/end

    Otra forma semejante a la anterior
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Otra forma semejante a la anterior
     
    [1,2,3].each do |x|
      puts x*2                   
    end
    # Salida:
    # 2
    # 4
    # 6
     
     
    # Otra forma semejante a la anterior
    
    [1,2,3].each do |x|
      puts x*2                    
    end
    # Salida: 
    # 2
    # 4
    # 6
    

  • Variable que almacena un bloque

    Como muestra de lo anterior es posible asociar un bloque a una variable y pasársela a un método each, para que se ejecute por cada uno de los elementos de la colección.

    rear variables que almancene un proceso
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Crear variables que almancene un proceso
     
    p = Proc.new { |x| puts x*2 }
    [1,2,3].each(&p)            
     
    # Salida:
    # 2
    # 4
    # 6
     
     
    # Crear variables que almancene un proceso
    
    p = Proc.new { |x| puts x*2 }
    [1,2,3].each(&p)             
    
    # Salida: 
    # 2
    # 4
    # 6
    

    Nótese que la variable solo almacena el código que se va a ejecutar pero que no se ejecuta realmente hasta que dicha variable se pasa como parámetro al each. También es de apreciar que todos los métodos son equivalentes.

  • Comprobar si existe un valor en un array (mediante bloques).


    Comprobar si un elemento existe en un array
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Comprobar si un saludo elemento existe en un array
    # *****************************************************
     
    mensajes = ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
     
    puts( mensajes.any? {|e| e=~ /Maria/} )
    # Salida: true
     
     
    # *****************************************************
    # Comprobar si un saludo elemento existe en un array
    # *****************************************************
    
    mensajes = ["Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
    
    puts( mensajes.any? {|e| e=~ /Maria/} )
    # Salida: true
    

  • Eliminar elementos de un array (mediante bloques).

    Eliminar elementos de un array
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Eliminar elementos de un array (elimino a Maria y a Jose)
    # *****************************************************
     
    mensajes.delete_if {|e| e=~ /Maria|Jose/ }
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Pedro", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
     
     
    # *****************************************************
    # Eliminar elementos de un array (elimino a Maria y a Jose)
    # *****************************************************
    
    mensajes.delete_if {|e| e=~ /Maria|Jose/ } 
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Pedro", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
    

  • Cambiar un array a masculas (mediante bloques).

    Eliminar elementos de un array
     
    1
    2
    3
    4
    5
    6
    7
    8
    # *****************************************************
    # Eliminar elementos de un array (elimino a Maria y a Jose)
    # *****************************************************
     
    mensajes.delete_if {|e| e=~ /Maria|Jose/ }
     
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Pedro", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
     
     
    # *****************************************************
    # Eliminar elementos de un array (elimino a Maria y a Jose)
    # *****************************************************
    
    mensajes.delete_if {|e| e=~ /Maria|Jose/ } 
    
    puts mensajes.inspect
    # Salida: ["Hola Juan", "Hola Pedro", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
    

C#


C# es un lenguaje que en los últimos tiempos ha tenido una evolución muy rápida, nació con un paradigma orientado a objetos, muy parecido a Java, y ha sabido ganarse su identidad, agregando multitud de funcionalidades declarativas que lo hacen un lenguaje claro y sencillo.


Características declarativas de C#


Facilidad para declarar variables


En momento de la creación de variables se ven ciertos aspectos declarativos, como por ejemplo:

  • Declaración de arrays

    Declaración de Arrays
     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Puede probar este codigo en https://repl.it/languages/csharp
     
    using System;
     
     
    public static class Program
    {
        private static void Imprimir(this string[] arrString)
        {
            Console.WriteLine($"[ \"{String.Join("\", \"", arrString)}\"] ");
        }
     
        public static void Main()
        {
            string[] mensajes = new string[] { "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar" };
     
            //Salida: [ "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
     
            mensajes.Imprimir();
            Console.ReadLine();
        }
    }
     
     
    // Puede probar este codigo en https://repl.it/languages/csharp
    
    using System;
    
    
    public static class Program
    {
        private static void Imprimir(this string[] arrString)
        {
            Console.WriteLine($"[ \"{String.Join("\", \"", arrString)}\"] ");
        }
    
        public static void Main()
        {
            string[] mensajes = new string[] { "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar" };
    
            //Salida: [ "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar"]
    
            mensajes.Imprimir();
            Console.ReadLine();
        }
    }
    

    Salida: [ "Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar"]

  • Declaración de diccionarios

    Declaración de Diccionarios
     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    // Puede probar este codigo en https://repl.it/languages/csharp
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
     
    public static class Program
    {
        private static void Imprimir(this Dictionary<string, string> dicionario)
        {
            var lines = dicionario.Select(e => $"\t{e.Key}: {e.Value}");
     
            Console.WriteLine($"[{Environment.NewLine}{String.Join(Environment.NewLine, lines)}{Environment.NewLine}]");
        }
     
        public static void Main()
        {
            Dictionary<string, string> mensajes = new Dictionary<string, string>
            {
                ["saludo_juan"] = "Hola juan",
                ["saludo_maria"] = "Hola Maria",
                ["saludo_pedro"] = "Hola pedro",
            };
     
            //Salida:
            /*
             * [
             *   saludo_juan: Hola juan
             *   saludo_maria: Hola Maria
             *   saludo_pedro: Hola pedro
             * ]
             */
     
            mensajes.Imprimir();
            Console.ReadLine();
        }
    }
     
     
    // Puede probar este codigo en https://repl.it/languages/csharp
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    public static class Program
    {
        private static void Imprimir(this Dictionary<string, string> dicionario)
        {
            var lines = dicionario.Select(e => $"\t{e.Key}: {e.Value}");
    
            Console.WriteLine($"[{Environment.NewLine}{String.Join(Environment.NewLine, lines)}{Environment.NewLine}]");
        }
    
        public static void Main()
        {
            Dictionary<string, string> mensajes = new Dictionary<string, string>
            {
                ["saludo_juan"] = "Hola juan",
                ["saludo_maria"] = "Hola Maria",
                ["saludo_pedro"] = "Hola pedro",
            };
    
            //Salida:
            /*
             * [
             *   saludo_juan: Hola juan
             *   saludo_maria: Hola Maria
             *   saludo_pedro: Hola pedro
             * ]
             */
    
            mensajes.Imprimir();
            Console.ReadLine();
        }
    }
    

  • Declaración mediante el uso de var

    Existe una clausula especial para declarar variables sin necesidad de especificar el tipo:

    Uso de clausula var
     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    using System;
     
    public static class Program
    {
        public static void Main()
        {
            var mensaje = "Hola mundo";
            var mensajes = new string[]{"Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar"};
            var mensajesUnidos = String.Join("[\", \"", mensajes + "]");
            Console.ReadLine();
        }
    }
     
     
    using System;
    
    public static class Program
    {
        public static void Main()
        {
            var mensaje = "Hola mundo";
            var mensajes = new string[]{"Hola Juan", "Hola Maria", "Hola Pedro", "Hola Jose", "Hola Elias", "Hola Gustavo", "Hola Edgar"};
            var mensajesUnidos = String.Join("[\", \"", mensajes + "]");
            Console.ReadLine();
        }
    }
    

    Con esto se infiere el tipo (en tiempo de compilación) de la variable, en este caso son String y String[ ].

    En lo personal no me gusta un uso excesivo de var, ya que no queda claro el tipo de la variable(al momento de leer el código), sobre todo si es el resultado deuda función. Pero esto cambia cuando estamos funciones Linq, donde sin duda, es mucho más sencillo usar la cláusula var, que especificar el tipo.

Hasta ahora hemos visto algunos tipos de declaración, que nos ayudan a especificar la creación de objetos de forma más declarativo que imperativa, aunque es, desde luego, mucho menos declarativo que Ruby.

Expresiones Lamba


Clásicamente, en la programación imperativa, se tienen variables que apuntan a funciones (en lugar de a datos), de forma que se pueden almacenar la función que se va a llamar en una variable, y para realizar la llamada posteriormente, por ejemplo en C, seria de la siguiente forma:

Puntero a funciones
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
 
int sumar_por_2(int numero){
  //Suma dos al número especificado
  return numero + 2;
}
 
int multiplar_por_2(int numero){
  //Multiplica por dos el número especificado
  return numero *  2;
}
 
 
int main(void) {
 
  int valor=5;
 
  //Puntero a funcion que devuelve recibe un int y devuelve un int
  int (*funcion) (int);
 
  //La función apunta a sumar 2
  funcion = &sumar_por_2;
 
  printf("Valor función: %d\n",funcion(valor));
 
  //La función apunta a sumar 2
  funcion = &multiplar_por_2;
 
  printf("Valor función: %d\n",funcion(valor));
 
  // Salida:
  /*
  * Valor función: 7
  * Valor función: 10
  */ 
   
  return 0;
}
 
 
#include <stdio.h>

int sumar_por_2(int numero){
  //Suma dos al número especificado
  return numero + 2;
}

int multiplar_por_2(int numero){
  //Multiplica por dos el número especificado
  return numero *  2;
}


int main(void) {

  int valor=5;

  //Puntero a funcion que devuelve recibe un int y devuelve un int
  int (*funcion) (int);

  //La función apunta a sumar 2
  funcion = &sumar_por_2;

  printf("Valor función: %d\n",funcion(valor));

  //La función apunta a sumar 2
  funcion = &multiplar_por_2;

  printf("Valor función: %d\n",funcion(valor));

  // Salida:
  /*
  * Valor función: 7 
  * Valor función: 10
  */  
  
  return 0;
}

De origen Java, elimino esta funcionalidad por lo que no era posible tener variables que apuntaran a código, si no que era necesario tener un objeto, que contuviera el código que queríamos ejecutar, a continuación un ejemplo de swing, donde se ve la implementación mediante Listeners (se muestra un mensaje por pantalla, al pulsar un botón).

Nótese que para el botón “uno” se usa programación orientada a objetos para indicar que debe hacerse al pulsar dicho botón, creando una clase anónima Listener. Se incluye un botón “dos” que hace lo mismo mediante programación declarativa y expresiones lamba, es, como se ve, mucho menos código y mas compresible,

Ejemplo Listener
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.desdelashorasextras;
 
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
public class Codigo11 {
    JFrame frame;
    JButton boton1;
    JButton boton2;
 
    Codigo11() {
        frame = new JFrame();
        boton1 = new JButton("Pulsa el boton Uno !!");
        boton2 = new JButton("Pulsa el boton Dos!!!");
 
        frame.setSize(550, 500);
        frame.setLayout(null);
        frame.add(boton1);
        frame.add(boton2);
 
        boton1.setBounds(10, 100, 200, 60);
        //Configuro un evento de forma tradicional para cuando el usuario haga clic,
        //se ve que necesito crear una clase anonima que capture el evento
        boton1.addActionListener( ( new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frame, "Gracias desde el UNO!!");
            }
        }));
 
        boton2.setBounds(250, 100, 200, 60);
        //Misma situacion que la anterior pero usando programación declarativa
        //Disponible en las versiones modernas de Java
        boton2.addActionListener(e -> JOptionPane.showMessageDialog(frame, "Gracias desde el UNO!!"));
 
 
        frame.setVisible(true);
    }
 
    public static void main(String[] args) {
        new Codigo11();
    }
}
 
 
package com.desdelashorasextras;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Codigo11 {
    JFrame frame;
    JButton boton1;
    JButton boton2;

    Codigo11() {
        frame = new JFrame();
        boton1 = new JButton("Pulsa el boton Uno !!");
        boton2 = new JButton("Pulsa el boton Dos!!!");

        frame.setSize(550, 500);
        frame.setLayout(null);
        frame.add(boton1);
        frame.add(boton2);

        boton1.setBounds(10, 100, 200, 60);
        //Configuro un evento de forma tradicional para cuando el usuario haga clic,
        //se ve que necesito crear una clase anonima que capture el evento
        boton1.addActionListener( ( new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frame, "Gracias desde el UNO!!");
            }
        }));

        boton2.setBounds(250, 100, 200, 60);
        //Misma situacion que la anterior pero usando programación declarativa
        //Disponible en las versiones modernas de Java
        boton2.addActionListener(e -> JOptionPane.showMessageDialog(frame, "Gracias desde el UNO!!"));


        frame.setVisible(true);
    }

    public static void main(String[] args) {
        new Codigo11();
    }
}

Java estaba en lo correcto al usar un paradigma orientado a objetos puro, donde incluso las llamadas a código mediante variables tenían que pasar por un Listener o alguna estructura semejante, pero eso hacia el código demasiado largo, enredoso, y difícil de comprender. En versiones posteriores Java introdujo las expresiones lambda y comenzó a añadir funciones declarativas y de otra índole multiparadigma.

C# (oficialmente inspirado en C++, pero en muchos aspectos claramente basada en Java) introdujo desde su primera versión la posibilidad de que las variables apuntaran a direcciones de código (a funciones directamente) de una forma bastante imperativa al principio (mediante delegados y delegados anónimos), hasta convertirse en algo mucho más declarativo (mediante expresiones lambda)

Veamos directamente el uso de expresiones lambda de forma declarativa:

  • Calculo del cuadro de un array de enteros

    Cuadrado de cada número
     
    1
    2
    3
    4
    5
    6
    7
    8
    int[] numeros = new int[] { 1, 2, 3 };
     
    // Imprimir el cuadro de cada numero
    var cuadrados = numeros.Select(x => x * x);
     
    //Salida: ["1", "4", "9"]
     
    cuadrados.Imprimir();
     
     
    int[] numeros = new int[] { 1, 2, 3 };
    
    // Imprimir el cuadro de cada numero
    var cuadrados = numeros.Select(x => x * x);
    
    //Salida: ["1", "4", "9"]
    
    cuadrados.Imprimir();
    

    El método Select, selecciona los elementos de un array, aplicándoles una posible transformación, que viene especificado mediante una expresión lamba. En este caso por cada x recibida (que es cada uno de los elementos del array), lo multiplica por sí mismo.

    x=> x * x, en esta expresión x es algo así como el parámetro de la expresión lamba y x * x el resultado.

    Otro detalle interesante, es el método Select, no pertenece al tipo de datos int [] sino que es un método extensor. Los métodos extensores son métodos que se definen fuera de la definición de la clase, y amplían las capacidades de esta sin modificarla, a veces incluso son aplicables a interfaces, por ejemplo, este es el método extensor que nos permite imprimir el resultado de los cuadrados.

    Cuadrado de cada número
     
    1
    2
    3
    4
    private static void Imprimir(this IEnumerable<int> arraInt)
    {
          Console.WriteLine($"[\"{String.Join("\", \"", arraInt.Select(x => x.ToString()))}\"]");
    }
     
     
    private static void Imprimir(this IEnumerable<int> arraInt)
    {
          Console.WriteLine($"[\"{String.Join("\", \"", arraInt.Select(x => x.ToString()))}\"]");
    }
    

    El código anterior significa que para todas las clases que implemente IEnumerable <int> (Arrays, Listas, colecciones y demás), le agrega un método para imprimir su contenido, de forma que podemos llamarlo así:

    cuadrados.Imprimir();

    En el caso del Select y la expresión lambda asociada (x=> x * x) vemos como se combinan de forma muy simple y funcional la programación orientada a objetos y la declarativa, haciendo que el código resultante, es sencillo y fácil de entender.

  • Consultas a colecciones de forma declarativas

    Es posible aplicar consultas a colecciones de objetos por ejemplo podemos tener el objeto Persona, y podemos realizar transformaciones y selecciones de forma muy simple y clara.

    Consultas a colecciones de objetos
     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    // Creo una colección de personas.
    Persona[] personas = new Persona[]{
        new Persona { Nombre="Maria", Apellido="Jimenez"},
        new Persona { Nombre="Juan", Apellido="Perez"},
        new Persona { Nombre="Ana", Apellido="Moreno"},
        new Persona { Nombre="Ruben", Apellido="Gomez"},
        new Persona { Nombre="Pedro", Apellido="Sanchez"},
        new Persona { Nombre="Roberto", Apellido="Hernandez"}
    };
     
    // ********************************
    // Comprobar si un saludo elemento existe en un array
    // ********************************
     
    Console.WriteLine($"{personas.Any(x => x.Nombre == "Maria")}");
     
    // Salida: true
     
    // ********************************
    // Selecciono elementos no sean Maria o Juan
    // ********************************
     
    var sinMariaJose = personas
            .Where(x => x.Nombre != "Maria" && x.Nombre != "Jose")
            .Select(x => x);
     
    sinMariaJose.Imprimir();
     
    //Salida [ "Juan Perez", "Ana Moreno", "Ruben Gomez", "Pedro Sanchez", "Roberto Hernandez"]
     
    // *****************************************************
    // Devolver un array de elementos a mayusculas
    // *****************************************************
     
    var mayusculas = personas
        .Select(x =>
            new Persona
            {
                Nombre = x.Nombre.ToUpper(),
                Apellido = x.Apellido.ToUpper(),
            }
        );
     
    mayusculas.Imprimir();
     
    // Salida: ["HOLA JUAN", "HOLA PEDRO", "HOLA ELIAS", "HOLA GUSTAVO", "HOLA EDGAR"]
     
    //*****************************************************
    // Buscar un elemento que acaben en o
    // Busco aquellos mensajes que acaben con la letra o, aquí se usan expresiones lamba, las veremos después
    //*****************************************************
     
    var elementosAcabanO = personas
        .Where(x => Regex.IsMatch(x.Nombre, @"o$"))
        .Select(x => x);
     
    elementosAcabanO.Imprimir();
     
    // Salida: [ "Pedro Sanchez", "Roberto Hernandez"]
     
     
     // Creo una colección de personas.
    Persona[] personas = new Persona[]{
        new Persona { Nombre="Maria", Apellido="Jimenez"},
        new Persona { Nombre="Juan", Apellido="Perez"},
        new Persona { Nombre="Ana", Apellido="Moreno"},
        new Persona { Nombre="Ruben", Apellido="Gomez"},
        new Persona { Nombre="Pedro", Apellido="Sanchez"},
        new Persona { Nombre="Roberto", Apellido="Hernandez"}
    };
    
    // ********************************
    // Comprobar si un saludo elemento existe en un array
    // ********************************
    
    Console.WriteLine($"{personas.Any(x => x.Nombre == "Maria")}");
    
    // Salida: true
    
    // ********************************
    // Selecciono elementos no sean Maria o Juan
    // ********************************
    
    var sinMariaJose = personas
            .Where(x => x.Nombre != "Maria" && x.Nombre != "Jose")
            .Select(x => x);
    
    sinMariaJose.Imprimir();
    
    //Salida [ "Juan Perez", "Ana Moreno", "Ruben Gomez", "Pedro Sanchez", "Roberto Hernandez"]
    
    // *****************************************************
    // Devolver un array de elementos a mayusculas
    // *****************************************************
    
    var mayusculas = personas
        .Select(x =>
            new Persona
            {
                Nombre = x.Nombre.ToUpper(),
                Apellido = x.Apellido.ToUpper(),
            }
        );
    
    mayusculas.Imprimir();
    
    // Salida: ["HOLA JUAN", "HOLA PEDRO", "HOLA ELIAS", "HOLA GUSTAVO", "HOLA EDGAR"]
    
    //*****************************************************
    // Buscar un elemento que acaben en o
    // Busco aquellos mensajes que acaben con la letra o, aquí se usan expresiones lamba, las veremos después
    //*****************************************************
    
    var elementosAcabanO = personas
        .Where(x => Regex.IsMatch(x.Nombre, @"o$"))
        .Select(x => x);
    
    elementosAcabanO.Imprimir();
    
    // Salida: [ "Pedro Sanchez", "Roberto Hernandez"]
    

    C# tiene una sintaxis alternativa para las consultas parecida a SQL (aunque en lo personal no es mi forma favorita, simplifica la lectura):

    Consulta alternativa
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // ********************************
    // Selecciono elementos no sean Maria o Juan
    // ********************************
     
    var sinMariaJoseAlternativo = from x in personas
                                    where x.Nombre != "Maria" && x.Nombre != "Jose"
                                    select x;
     
    sinMariaJoseAlternativo.Imprimir();
     
     
    // ********************************
    // Selecciono elementos no sean Maria o Juan
    // ********************************
    
    var sinMariaJoseAlternativo = from x in personas
                                    where x.Nombre != "Maria" && x.Nombre != "Jose"
                                    select x;
    
    sinMariaJoseAlternativo.Imprimir();
    

  • Consultas a colecciones de forma paralela

    Generalmente la programación paralela es complicada, hay muchos puntos a tener en cuenta, como sincronizaciones, esperas, candados, pero mediante C# y de forma declarativa, podemos recorrer un array de forma paralela, simplemente exponiéndolo con la opción “AsParallel()”.

    Consulta paralela
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // ********************************
    // Selecciono elementos no sean Maria o Juan
    // ********************************
     
    var sinMariaJose = personas.AsParallel()
            .Where(x => x.Nombre != "Maria" && x.Nombre != "Jose")
            .Select(x => x);
     
    sinMariaJose.Imprimir();
     
     
    // ********************************
    // Selecciono elementos no sean Maria o Juan
    // ********************************
    
    var sinMariaJose = personas.AsParallel()
            .Where(x => x.Nombre != "Maria" && x.Nombre != "Jose")
            .Select(x => x);
    
    sinMariaJose.Imprimir();
    

    Salida: [ "Juan Perez", "Ana Moreno", "Ruben Gomez", "Pedro Sanchez", "Roberto Hernandez"]

    Simplemente poniendo AsParallel() a continuación del array se recorre de forma paralela, optimizando el proceso entre los varios núcleos de una CPU.

Las expresiones lambda pueden ser datos y código


Como vemos las expresiones lambda son ideales para expresar lo que queremos conseguir, en lugar de cómo obtenerlo.

Lambda nos permite unir la programación en C# y las consultas en SQL, mediante alguno ORM, por ejemplo Entity Framework.

Por ejemplo teniendo la base de datos Northwind, pudiéramos hacer una consulta a la tabla employe de la siguiente forma:

Consulta paralela
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using (var context = new NorthwindEntities())
{
    // Descomente la siguiente linea para mostar las sentencias SQL generadas
    //  context.Database.Log = Console.WriteLine;
 
    var consulta = from e in context.Employees
                    where e.FirstName.Contains("a")
                    select (e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);
 
    // El codigo anterior equivale al siguiente
    // var consulta = context.Employees
    //    .Where(e => e.FirstName.Contains("a"))
    //    .Select(e => e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);
 
    //SQL GENERADO
    // SELECT CASE
    //      WHEN ([Extent1].[TitleOfCourtesy] IS NULL)
    //          THEN N''
    //      ELSE [Extent1].[TitleOfCourtesy]
    //      END + N' ' + [Extent1].[FirstName] + N' ' + [Extent1].[LastName] AS [C1]
    // FROM [dbo].[Employees] AS [Extent1]
    // WHERE [Extent1].[FirstName] LIKE N'%a%'
 
    var resultado = consulta.ToArray();
 
    Console.WriteLine(String.Join(Environment.NewLine, resultado));
 
}
 
 
using (var context = new NorthwindEntities())
{
    // Descomente la siguiente linea para mostar las sentencias SQL generadas
    //  context.Database.Log = Console.WriteLine;

    var consulta = from e in context.Employees
                    where e.FirstName.Contains("a")
                    select (e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);

    // El codigo anterior equivale al siguiente
    // var consulta = context.Employees
    //    .Where(e => e.FirstName.Contains("a"))
    //    .Select(e => e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);

    //SQL GENERADO
    // SELECT CASE
    //      WHEN ([Extent1].[TitleOfCourtesy] IS NULL)
    //          THEN N''
    //      ELSE [Extent1].[TitleOfCourtesy]
    //      END + N' ' + [Extent1].[FirstName] + N' ' + [Extent1].[LastName] AS [C1]
    // FROM [dbo].[Employees] AS [Extent1]
    // WHERE [Extent1].[FirstName] LIKE N'%a%'

    var resultado = consulta.ToArray();

    Console.WriteLine(String.Join(Environment.NewLine, resultado));

}

Salida:

Ms.Nancy Davolio

Dr.Andrew Fuller

Ms.Janet Leverling

Mrs.Margaret Peacock

Mr.Michael Suyama

Ms.Laura Callahan

Ms.Anne Dodsworth


El código anterior realiza las siguientes acciones:


  1. Se conecta la base de datos Northwind

  2. Ejecuta un código de consulta SQL sobre la tabla empleados

  3. Recupera el nombre completo de todos los empleados

  4. Cierra la conexión.


Todo esto sin realizar los pesados pasos para conectarse a una base de datos, y ejecutar código SQL, todo se especifica de forma declarativa.

La parte declarativa puede expresarse de dos formas, y las dos son exactamente equivalentes.

Primera forma:

Consulta Linq
 
1
2
3
var consulta = from e in context.Employees
                where e.FirstName.Contains("a")
                select (e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);
 
 
var consulta = from e in context.Employees
                where e.FirstName.Contains("a")
                select (e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);

Segunda forma:

Consulta alternativa
 
1
2
3
var consulta = context.Employees
    .Where(e => e.FirstName.Contains("a"))
    .Select(e => e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);
 
 
var consulta = context.Employees
    .Where(e => e.FirstName.Contains("a"))
    .Select(e => e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);

Ahora bien, no es muy inteligente, traer una tabla completa en memoria (de la maquina cliente), y realizar un buscara allá, pero eso es lo que pensaríamos al ver la sentencia tal (sobre todo en la segunda forma, pero realmente hacen lo mismo). El caso es que la expresión lambda en este escenario no hace la función de ser código, si no datos, que serán convertidos en una sentencia SQL, de forma completa al momento de ejecutarse, es decir mediante código podemos expresar una consulta SQL de forma íntegra y declarativa.




Si te ha gustado la entrada, ¡Compártela! ;-)

Nota: Puedes encontrar todo el código fuente de este artículo en https://github.com/jbautistamartin/ParadigmasTiposLenguajes