Bienvenidos sean a este post, hoy hablaremos sobre el concepto de funciones puras.
En el post anterior mencionamos que el paradigma de programacion funcional considera de manera predeterminada que las funciones son puras. Y como mencionamos en el post anterior, una funcion pura es aquella que no muta su estado. Esto puede llevarnos a pensar que las funciones puras pueden ser consideradas como mas lentas comparadas con sus contrapartes no puras. Sin embargo, ellas son mejores porque evitan a los bugs que pueden ocurrir con las modificaciones de estado. Los programas trabajan con datos, por lo tanto se componen de una funcionalidad de modificaciones de estados que lleva a algun resultado esperado para el usuario.
Cuando hablamos del paradigma de programacion funcional en este post, mencionamos que la programacion orientada a objetos descomponia un programa en objetos, cada cual con un conjunto de caracteristicas especiales, siendo la principal de estas el estado. Una de las partes cruciales de OOP es la modificacion del estado de un objeto enviando mensajes a este, mediante el llamado a sus metodos. Por eso usualmente una invocacion de una funcion miembro nos lleva a la modificacion del estado de un objeto.
En la programacion funcional, organizamos el codigo en una coleccion de funciones puras, donde cada uno tendra su proposito y es independiente de los otros. Para poder entender el concepto de lo comentado anteriormente, vamos a suponer un ejemplo. Supongamos que debemos manejar objetos de usuarios en un programa y cada objeto contiene distintos datos del mismo. Veamos como puede ser el struct para los objetos:
struct User
{
int edad;
std::string nombre;
std::string telefono;
std::string email;
};
Simplemente tenemos un struct con distintas variables para almacenar los valores del mismo. Ahora supongamos, que tenemos una funcion encargada de incrementar la edad del usuario una vez al año. Veamos la siguiente funcion:
void cambiar_edad(User& u)
{
u.edad = u.edad + 1;
}
Esta funcion toma al objeto del tipo User como referencia y actualiza al objetivo original. Este no es el caso de la programacion funcional, en lugar de tomar el objeto original por referencia y mutando su valor. Veamos esta nueva funcion:
User cambiar_edad_pura(const User& u)
{
User tmp{u};
tmp.edad = tmp.edad + 1;
return tmp;
}
Esta funcion devuelve un objeto User totalmente diferente con las mismas propiedades, excepto por la propiedad edad que fue actualizada. Si lo comparamos con la funcion anterior, podemos considerarla como ineficiente, aunque una ventaja de este enfoque es que muestra las operaciones de una manera mas clara, lo cual agradeceremos al momento de hacer el debug. Con esta funcion tenemos garantizado que no se modificara el objeto original. Pero esta funcion se puede mejorar de la siguiente manera:
User cambiar_edad_pura(User u)
{
u.edad = u.edad + 1;
return u;
}
Con esta nueva funcion podemos tomar al objeto como valor, y no como referencia, incrementamos el valor de edad y devolvemos el objeto, sin necesidad de un objeto temporal como era en el caso anterior porque el argumento es ahora una copia del objeto original. Para comprobar si es pura, cuando la llamemos multiples veces siempre debe devolver el mismo valor. Vamos a crear un codigo con la ultima funcion para ver su funcionamiento:
#include <iostream>
#include <string>
struct User
{
int edad;
std::string nombre;
std::string telefono;
std::string email;
};
User cambiar_edad_pura(User u)
{
u.edad = u.edad + 1;
return u;
}
int main()
{
User etortore{.edad{33}, .nombre{"Enzo Tortore"}};
auto cambiar{cambiar_edad_pura(etortore)};
std::cout << cambiar.edad << std::endl;
cambiar = cambiar_edad_pura(etortore);
std::cout << cambiar.edad << std::endl;
return 0;
}
Simplemente agregamos el struct y la ultima funcion que comentamos anteriormente. En el main, cremos un objeto del struct, para luego crear un objeto donde aplicamos la funcion sobre el usuario y mostramos el valor de edad. Con el segundo objeto creado volvemos a almacenar el resultado del llamado a la funcion y el objeto para mostrarlo nuevamente. Veamos como es la salida:
$ ./puro
34
34
$
Un beneficio de este tipo de conducta en una funcion, es que siempre se comporta de la misma manera cada vez que se lo llama con los mismos datos de entrada. Como mencionamos en reiteradas oportunidades, esto nos permite diseñar nuestro programa descomponiendolo en pequeñas funciones, donde cada cual tiene un proposito claro. Pero esto conlleva a una sobrecarga en terminos del objeto temporal adicional, porque por lo general un diseño involucra tener un almacenamiento generalizado que contenga el estado del programa, el cual es actualizado indirectamente por las funciones puras. Como vimos en el ultimo caso, cada invocacion de una funcion pura devuelve el objeto modificado como un nuevo objeto que se puede almacenar en caso de ser necesario.
En resumen, hoy hemos repasado y adentrado un poco mas en las funciones puras, sus pros y contras, asi como un ejemplo simple para ver como es su conducta. Espero les haya sido 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.


Donation
It’s for maintenance of the site, thanks!
$1.50
