Sunday, February 4, 2007

Un abreboca al poder de la programación funcional

Una de las razones por la cual la programación funcional es considerada poderosa es por la facilidad con la que se puede derivar nuevo código a partir del ya existente. Por ejemplo, una de las tareas mas comunes durante el desarrollo de programas es operar sobre una colección de datos, bien sea para generar una nueva colección, modificar la ya existente o generar una estructura nueva derivada de esta.

Así, si quisieramos la sumatoria de los valores en un arreglo numérico el código (en python) seria:

suma = 0
for x in arreglo :
suma += x

Para elevar al cuadrado los componentes de un arreglo, la solución que implementaría un programador de C escribiendo python seria:

for i in len(arreglo) :
arreglo[i] = arreglo[i]**2

(python tienen una forma mas elegante de lograrlo, pero es precisamente una forma "prestada" de lenguajes con paradigmas diferentes al imperativo).

Estos códigos son representativos de las soluciones imperativas: crear un bucle que recorra el arreglo, operando sobre cada elemento para generar el resultado deseado. Un programador de C o Java con algunos años de experiencia habrá escrito lineas semejantes a esas un millar de veces. El problema esta en que precisamente la tarea es tan común que forma un patrón ideal para ser resumido.

Ahora, como podría aislarse esa solución en un lenguaje como C o Java, de manera que el programador solo tuviera que escribir la parte del problema que cambia (el suma +=x o el arreglo[i] = arreglo[i]**2)? Dado que la estructura semántica del C y el java están fijadas y no lo permiten directamente, la única posibilidad seria por medio de una librería. La librería implementaría la parte común (el bucle) y recibiría como parámetro un elemento (un objeto en Java o un apuntador a una función en C) que contenga el pedazo de programa que varía.

Y he allí el problema. Declarar una función en C es mas trabajo que escribir el bucle directamente. Definir una clase en Java que implemente el comportamiento deseado, instanciarla y pasar el objeto a la librería no es una opción: es una penitencia.

La mayoría de los lenguajes funcionales por otra parte permiten la creación de funciones con un costo mínimo para el programador, razón por la cual hacer una librería con los tipos mas comunes de bucles no solo es viable, sino la forma natural de proceder.

El primer caso mostrado, la suma de los valores en un arreglo, es de hecho un caso particular de un tipo de bucles llamado fold (también llamado reduce). Este caso se caracteriza por dos elementos: un valor a ser devuelto en caso que el arreglo sea de longitud cero, y una función que tome dos valores y devuelve uno, la cual es usada para "reducir" el arreglo. En nuestro caso, el valor a ser devuelto en caso que el arreglo este vació es cero y la función que reduce el arreglo es la suma.

Así, expresar ese bucle en python de forma funcional seria

reduce(real.__add__, arreglo, 0.0)


En Haskell, uno de los lenguajes mas elegantes, seria

foldr (+) 0.0 arreglo

En ambos casos el significado es el mismo: si el arreglo esta vació, devuelve 0.0, sino suma sus elementos. Si bien este programa puede resultar de mas difícil lectura (sobre todo debido a la costumbre imperativa), también ofrece algunas ventajas:

  • Es mas conciso
  • No usa variables temporales
  • Puede ser manipulado programaticamente
El ultimo punto tendrá un impacto gigante en la forma en que programamos en los años venideros. Dado que la función fold aisla las partes que varían en un bucle y homogeniza la estructura, es mas amistosa desde el punto de vista matemático. Si todos los bucles son expresados con fold y otras pocas estructuras semejantes, el código puede ser transformado por un compilador inteligente siguiendo una serie de teoremas, obteniendo un código super-optimizado.

No comments: