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 | 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 | mensajes = [ 'Hola Juan' , 'Hola Maria' , 'Hola Pedro' ]
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 = {}
hash = {
saludo_juan: 'Hola juan' ,
saludo_maria: 'Hola Maria' ,
saludo_pedro: 'Hola pedro' ,
}
puts hash.inspect
|
|
|
# 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 | mensajes << 'Hola Jose'
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | mensajes[ 6 ] = 'Hola Elias'
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | mensajes << 'Hola Gustavo'
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | mensajes.unshift( 'Hola Roberto' )
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | encontrados=mensajes.select {|e| e=~/o$/}
puts encontrados.inspect
|
|
|
# *****************************************************
# 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 | mensajes.delete_if {|e| !e }
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | mensajes << 'Hola Edgar'
mensajes << 'Hola Luz'
puts mensajes.inspect
mensajes.shift()
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | mensajes.push( 'Hola Ruben' )
puts mensajes.inspect
mensajes.pop()
puts mensajes.inspect
|
|
|
# *****************************************************
#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 | [ 1 , 2 , 3 ]. each { |x| puts x* 2 }
|
|
|
#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 | [ 1 , 2 , 3 ]. each do |x|
puts x* 2
end
|
|
|
# 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 | p = Proc . new { |x| puts x* 2 }
[ 1 , 2 , 3 ]. each (&p)
|
|
|
# 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 | mensajes = [ "Hola Juan" , "Hola Maria" , "Hola Pedro" , "Hola Jose" , "Hola Elias" , "Hola Gustavo" , "Hola Edgar" ]
puts( mensajes.any? {|e| e=~ /Maria/} )
|
|
|
# *****************************************************
# 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 | mensajes.delete_if {|e| e=~ /Maria|Jose/ }
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | mensajes.delete_if {|e| e=~ /Maria|Jose/ }
puts mensajes.inspect
|
|
|
# *****************************************************
# 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 | 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" };
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 | 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" ,
};
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){
return numero + 2;
}
int multiplar_por_2( int numero){
return numero * 2;
}
int main( void ) {
int valor=5;
int (*funcion) ( int );
funcion = &sumar_por_2;
printf ( "Valor función: %d\n" ,funcion(valor));
funcion = &multiplar_por_2;
printf ( "Valor función: %d\n" ,funcion(valor));
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 );
boton1.addActionListener( ( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frame, "Gracias desde el UNO!!" );
}
}));
boton2.setBounds( 250 , 100 , 200 , 60 );
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 };
var cuadrados = numeros.Select(x => x * x);
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 | 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" }
};
Console.WriteLine($ "{personas.Any(x => x.Nombre == " Maria ")}" );
var sinMariaJose = personas
.Where(x => x.Nombre != "Maria" && x.Nombre != "Jose" )
.Select(x => x);
sinMariaJose.Imprimir();
var mayusculas = personas
.Select(x =>
new Persona
{
Nombre = x.Nombre.ToUpper(),
Apellido = x.Apellido.ToUpper(),
}
);
mayusculas.Imprimir();
var elementosAcabanO = personas
.Where(x => Regex.IsMatch(x.Nombre, @"o$" ))
.Select(x => x);
elementosAcabanO.Imprimir();
|
|
|
// 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 | 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 | 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())
{
var consulta = from e in context.Employees
where e.FirstName.Contains( "a" )
select (e.TitleOfCourtesy + " " + e.FirstName + " " + e.LastName);
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:
-
Se conecta la base de datos Northwind
-
Ejecuta un código de consulta SQL sobre la tabla empleados
-
Recupera el nombre completo de todos los empleados
-
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.