Siempre
resulta interesante representar en un lenguaje de un paradigma
determinado las construcciones típicas de otro paradigma
diferente. Por ejemplo, se puede pensar en cómo representar
funciones lambda
en C++.
Tomando un ejemplo del libro "C++
Template Metaprogramming": si se pretende iterar a través de un
contenedor procesando de alguna manera cada dato en el mismo, uno puede
hacerlo utilizando un iterador y llamando a una función
particular para esa tarea:
code:
#include <iostream>
#include <map>
typedef map<string, string> PhoneList;
typedef PhoneList::value_type PhonePair;
void eachphone(const PhonePair &item) {
cout << item.first << " : " << item.second <<
endl;
}
void demoMapAlgo() {
PhoneList phones;
phones[string("Jeff")] = string("110-555-1234");
phones[string("Angie")] = string("800-123-4567");
phones[string("Dylan")] = string("123-867-5309");
phones[string("Amy")] = string("888-111-1111");
for_each(phones.begin(), phones.end(), eachphone);
}
Esto es muy común, y en general dichas funciones particulares se
utilizarán muy pocas veces (acaso nunca) fuera del código
subsiguiente a su declaración. No solo es común al iterar
sobre contenedores, sino también al manejar excepciones,
trabajar con eventos y muchos otros campos. Todo esto atenta contra la
motivación inicial de utilización de funciones y
procedimientos en la programación estructurada, que era la de
identificar patrones comunes de acciones para encapsularlas dentro de
un ente único que luego podía ser reutilizado en los
diferentes lugares donde se necesitara dicho patrón de
acción.
Sería mejor no tener la necesidad de definir funciones como la eachphone
de arriba, que agregará un símbolo más al binario y
más código al segmento de texto (código que
quizá sea raras veces visitado), que cargará más
el espacio de nombres actual y que le restará localidad
(autocontención) a la función demoMapAlgo.
Por supuesto se puede argumentar que lo anterior se podría
manejar sin necesidad de definir eachphone:
code:
PhoneList::iterator iter = phones.begin();
while (iter != phones.end()) {
cout << iter->first << " : " <<
iter->second << endl;
iter++;
}
Pero claramente aquí se está cambiando un problema por
otro, pues se ha perjudicado la legibilidad del código (en este
ejemplo minúsculo se puede seguir, pero en un programa real las
cosas se complican exponencialmente con construcciones como la de
arriba).
Sin embargo, con un uso inteligente de templates,
se arreglará el problema mediante una solución elegante.
Como la función eachphone será poco reutilizada,
no vale la pena darle la categoría de función 'posta', ni
siquiera se le dará un nombre, será una función lambda:
code:
#include <iostream>
#include <map>
#include <boost/format.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/algorithm.hpp>
#include <boost/lambda/if.hpp>
typedef map<string, string> PhoneList;
typedef PhoneList::value_type PhonePair;
void demoMapLambda() {
// idem anterior
for_each(phones.begin(), phones.end(),
cout << bind(&PhonePair::first, _1) << " : " <<
bind(&PhonePair::second, _1) << "\n"
);
}
Observar que esa cosa extraña que parece una sentencia en el
tercer argumento del for_each, de hecho no lo es (no tiene
punto y coma al final), es una expresión. El template bind
está definido en la biblioteca boost
(http://boost.sourceforge.net, un conjunto de bibliotecas C++ de
excelente calidad). Cuando el operador << evalúa de
izquierda a derecha, al buscar su segundo operando, aparece el bind,
con lo cual se utiliza su definición en la biblioteca, y
allí comienza la magia. El _1 representa una variable que
toma el valor del item actualmente visitado en el contenedor. Dicho
item será un par, el primer bind sacará la primera
componente, el segundo bind sacará la segunda.
En lugar de utilizar el bind como arriba, se puede utilizar en una
expresión lambda
una constante:
code:
for_each(phones.begin(), phones.end(), cout << constant("Hello ")
<< " World!\n"
);
o también una variable:
code:
int i = 0;
for_each(phones.begin(), phones.end(), cout << var(i++) << "
elephants...\n"
);
Como dije arriba, estas facilidades ofrecidas por la biblioteca boost
son muy elegantes y fáciles de utilizar (no así de
fácil entender por qué trabajan como trabajan). Para
quienes quieran ir investigando los detalles de la
implementación vean:
http://cvs.sourceforge.net/viewcvs.py/boost/boost/boost/lam
bda/
en particular los módulos en el subdirectorio detail/. Como
puede verse en detail/lambda_functor_base.hpp la biblioteca puede
aplicar functores de hasta aridad 10 (_1, _2, ..., _10).
Saludos,
Duilio.
|