martes, 29 de junio de 2010

Macros como ligazón semántica

Existen multitud de ocasiones en los cuales hay que sincronizar dos (o más) partes de un mismo programa. Generalmente los lenguajes de programación actuales no permiten expresar este tipo de invariantes fácilmente. Sin embargo, existe un pequeño truco para hacerlo mediante macros.

El problema

Supongamos que tenemos una enumeración

enum E { A, B, C };

Y queremos tener una lista con los nombres de los elementos de la enumeración.

static char *N[] = {"A", "B", "C"};

¿Qué pasa si años después en un ciclo de mantenimiento hay que añadir un nuevo elemento a la enumeración?

enum E { A, D, B, C };

Existe la posibilidad de que nos olvidemos de actualizar la lista de nombres. Nos encontramos con que el elemento D ahora tiene nombre "B". Es un error que se detectará (o no) ejecutando el programa y probándolo.

La solución

Lo que tenemos que hacer es tener una única lista y derivar las otras dos a partir de esta primera. La dificultad reside en que los elementos del lenguaje de programación que estamos usando son muy distintos entre sí: por un lado símbolos y por otro cadenas literales.

La única forma de actuar es mediante un sistema que trabaje indiferentemente sobre ambos tipos de elementos. Ese sistema en muchos lenguajes es el preprocesador de macros.

#define VALORES X(A, "A") X(B, "B") X(C, "C")

Ahora transformamos esos valores definiendo la macro X a lo que queramos (no nos olvidemos de la coma).

#define X(x, y) x,
enum E { VALORES };
#undef X

El preprocesador primero sustituirá VALORES por toda la lista

#define X(x, y) x,
enum E { X(A, "A") X(B, "B") X(C, "C") };
#undef X

Luego verá que aún tiene X por evaluar y lo hará:

#define X(x, y) x,
enum E { A, B, C, };
#undef X

Luego podrá descartar X ya que no se usa.

enum E { A, B, C, };

Casi todos los lenguajes de programación permiten una coma terminal para facilitar la construcción de herramientas que generen código fuente automáticamente. Como en este caso. Esta coma terminal se ignora por lo que al final tenemos lo que queríamos.

enum E { A, B, C };

Conclusiones

Es inmediato ver que si en vez de usar esa definición de X hubiéramos usado otra, tendríamos nuestra lista de nombres.

#define X(x, y) y,
static char *N[] = { VALORES };
#undef X

También es inmediato comprobar que hemos ligado la generación de la enumeración con la generación de la lista. De esta forma, si modificamos nuestros valores, automáticamente se modificarán ambas partes del código.

#define VALORES X(A, "A") X(D, "D") X(B, "B") X(C, "C")

Es justo lo que queríamos: ligar semanticamente dos partes del código de forma automática. Ahora es imposible que nos confundamos dándole a D el nombre "B" como ocurría antes.

Referencias: Walter Bright - The X Macro

0 comentarios:

Publicar un comentario