Anuncios

Bienvenidos sean a este post, hoy analizaremos por que usar lo mencionado en el titulo.

Anuncios
Anuncios

En este post hablamos sobre como es el paradigma de la programacion funcional. Y en este otro post hablamos sobre ranges y como estos colaboran con el paradigma anterior. Si los condicionales le dan razonamiento a nuestros codigos, este paradigma le introducira conciencia. Si los comparamos, notaremos que los codigos seran mas chicos que sus contrapares imperativas. Recuerden que a menor codigo menos probabilidades de bugs. Las funciones no mutan nada, lo cual permite facilitarnos las paralelizaciones. Pero una de las principales preocupaciones de los programas concurrentes es que las tareas concurrentes deben compartir datos mutables entre ellos. Esto origina que la mayoria de las veces necesitemos sincronizarlos con threads mediante mutex.

Anuncios
Anuncios

La programacion funcional nos libera de la sincronizacion explicita, permitiendo correr codigo en multiples threads sin adaptarlo. El paradigma considera a todas las funciones como pura, y como dijimos antes estas no mutan a los datos. Simplemente toman un valor de entrada, lo procesan y devuelven el ressultado de ese procesamiento. Y en su version mas pura, el resultado devuelto es del mismo tipo al de entrada, sin importar las cantidad de veces que sea invocado. Por esta razon, cada vez que hablamos sobre programacion funcional debemos tomar a todas las funciones como puras de manera predeterminada. Veamos la siguiente funcion de ejemplo:

double cuadrado(double num) { return num * num; }
Anuncios

Esta funcion recibe un valor de tipo double, devuelve el cuadrado del valor recibido, y del mismo tipo de dato. Una funcion tan simple como esta puede llevarnos a pensar que puede correr mas lenta pero en ciertos escenarios puede brindarnos una solucion mas rapida. Sin embargo, para adaptarlo correctamente al paradigma debemos forzarnos a pensar funcionalmente. Veamos el siguiente ejemplo:

#include <iostream>
#include <vector>
#include <cmath>

void calcular_sqrts(std::vector<double>& v)
{
        for(auto& e : v)
        {
                e = std::sqrt(e);
                std::cout << e << std::endl;
        }
}

int main()
{
        std::vector<double> vec{1.1,2.2,4.3,5.6,2.4};
        calcular_sqrts(vec);

        return 0;
}
Anuncios

Primero tendremos una funcion que recibira un vector con distintos valores y calcularemos la raiz cuadrada de cada elemento en el vector. En el main, definimos un vector con una serie de valores y llamamos a la funcion anterior. Compilemos y veamos como es la salida:

tinchicus@dbn001vrt:~/lenguajes/cpp/06$ ./raices
1.04881
1.48324
2.07364
2.36643
1.54919
tinchicus@dbn001vrt:~/lenguajes/cpp/06$
Anuncios

Realizo la tarea solicitada pero el vector que recibimos es por referencia, por lo tanto todo cambio que realicemos en la funcion se aplican a la coleccion original. Por lo tanto, esta funcion no es pura porque muta al vector de entrada. Para poder convertirla a pura, tendriamos que realizar el siguiente cambio en el codigo:

#include <iostream>
#include <vector>
#include <cmath>

std::vector<double> calcular_sqrts(std::vector<double>& v)
{
        std::vector<double> nuevo;
        for(auto& e : v)
                nuevo.push_back(std::sqrt(e));

        return nuevo;
}

int main()
{
        std::vector<double> vec{1.1,2.2,4.3,5.6,2.4};
        for(auto& e : calcular_sqrts(vec))
                std::cout << e << std::endl;

        return 0;
}
Anuncios
Anuncios

El primer cambio es en la funcion, donde la pasamos de void a vector. Creamos una nueva variable y mediante un bucle calcularemos la raiz cuadrada de elemento y lo almacenamos en la nueva variable para finalmente devolver todos los valores almacenados. En el main, ahora tenemos un bucle donde tomaremos la variable devuelta y pasaremos por cada uno de los elementos almacenados y la mostraremos en consola. si lo compilan y ejecutan deben tener la misma salida que antes. Si bien ahora tenemos una funcion pura, perdio un poco sobre la funcionalidad porque ahora debimos agregar un poco mas de codigo para ver la informacion. Y debemos reducirlo para que sea mas funcional, para ello cambiemos el codigo de la siguiente manera:

#include <iostream>
#include <vector>
#include <cmath>
#include <ranges>

int main()
{
        std::vector<double> vec{1.1,2.2,4.3,5.6,2.4};
        auto raices = [](double i){ return std::sqrt(i);};
        for(auto e : vec | std::views::transform(raices))
                std::cout << e << std::endl;

        return 0;
}
Anuncios

En este codigo implementamos a ranges. El primer cambio es la eliminacion de la funcion para calcular las raices. En el main, creamos una variable donde almacenamos una funcion que devuelve la raiz cuadrada del valor recibido. En el bucle aplicamos a transform para generar un view con la variable anterior y nos devuelve el valor para mostrarlo en cada pasada. Si lo compilan y ejecutan deben seguir teniendo la misma salida.

Anuncios

Con este ultimo codigo, no solamente achicamos el codigo sino que tambien eliminamos variables intermediarias. Como dijimos, transform genera un view donde trabajamos todo. Este no es otra coleccion con todos los elementos transformados del vector original. Observen como del codigo inicial lo transformamos a programacion funcional, y reducimos nuestro codigo a su minima expresion y la variable raices podemos considerarlo como una funcion pura.

Anuncios

En resumen, hoy hemos visto por que usar programacion funcional, algunos conceptos basicos, asi como tambien algunas nociones basicas sobre las funciones, y luego una serie de ejemplos para verlo porque debemos entender como pensar de esta manera. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Donatión

It’s for site maintenance, thanks!

$1.50