martes, 8 de febrero de 2011

Las tres cosas que diferencian funciones y macros

Hace tiempo hablé sobre las similitudes entre funciones y macros. Esta vez voy a hablar de sus diferencias.

Hay tres cosas que una macro puede hacer y una función no.

1) El control del flujo de ejecución

Una función no puede controlar qué pasa con sus argumentos. No puede controlar si se evalúan o no. No puede recuperarse de un error si uno de sus argumentos falla. No puede hacer nada. De hecho, cuando evaluamos

f(1+2, 3*4)

Lo que ocurre es que primero evaluamos los argumentos.

f(3, 12)

Y luego le pasamos el control a "f" que hará lo que tenga que hacer. Esto no es problema en un lenguaje funcional ya que no vamos a tener efectos secundarios de ningún tipo, pero sí que lo es en un lenguaje con efectos secundarios.

haz_dos_veces(print("a"))

Si "haz_dos_veces" es una función, vamos a evaluar primero los argumentos. Se imprimirá una vez "a" y pasaremos el resultado (posiblemente de tipo unitario) a la función que, haga lo que haga, no va a poder reimprimir la "a".

En el caso de que tuviéramos referencias a funciones entonces todo cambia. Ahora se puede pasar en el argumento la referencia a la función que hace lo que yo quiero.

define_funcion f() { print("a") }
haz_dos_veces(f)

Ahora sí que "haz_dos_veces" puede llamar dos veces a la función pasada e imprimir "a" dos veces. Así que cuando hay referencias a funciones esta diferencia entre macros y funciones no cuenta tanto; pero cuenta porque hay que envolver en una función el código que queremos pasar.

Las macros pueden hacerlo sin envolver nada.

define_macro haz_dos_veces(f) { f; f }
haz_dos_veces(print("a"))

Se expande a

print("a");print("a")


2) Manejo de entornos

El ejemplo más claro de esta posibilidad es la definición de símbolos en el entorno dinámico, aunque no sea lo más seguro y fácil de entender.

define_macro haz_a_cero() { a=0; }
a=1;
haz_a_cero(); //Expande a   a=0
print(a) //Imprime 0 porque ha sido modificado por la macro

Si fuera una función, hacer "a" valer cero dentro de la función sólo modifica el entorno local de la función.

define_funcion haz_a_cero() { a=0; }
a=1;
haz_a_cero();
print(a) //Imprime 1 porque no se ha modificado nada

Como ocurría con el apartado anterior, si tenemos referencias a variables (punteros), una función podría simular este comportamiento.

define_funcion haz_cero(x) {*x=0; }
a=1;
haz_cero(&a);
print(a) //Imprime 0

Sin embargo, la inspección del entorno la hemos tenido que hacer en la llamada con el "&a". La función no puede elegir qué símbolo va a cambiar. Si hubiéramos pasado "&b" la función no se quejaría y cambiaría "b". Con la macro esto no ocurría ya que ella misma inspeccionaba el entorno dinámico.


3) Procesado de código

Esta es la diferencia realmente grande. Los argumentos de una macro pueden significar algo completamente distinto a lo que se cree porque la macro puede procesar el código y cambiarlo. Por ejemplo, la macro "bromista" cambia los "+" por "-".

bromista(5+3) //Expande a  5-3

Con este tipo de transformaciones lo que realmente tenemos es la capacidad de modificar la semántica. En cierta forma, estamos ya acostumbrados. Supongamos un lenguaje en el que tengamos que declarar variables antes de usarlas, como JavaScript.

f(a=3); //Error, a no definido. 
var a=3; //Define a y le da el valor 3

El significado de "=" cuando se evalúa y cuando se escribe tras "var" es distinto. Si contemplamos "var" como una macro, lo que hace es buscar expresiones con la forma identificador-igual-valor. Para cada una de esas expresiones (en vez de evaluarlas como hizo la función "f"), define la variable en el entorno y le da ese valor.

Es esta capacidad de analizar y transformar el código de sus argumentos lo que hacen las macros únicas y superiores a las funciones.

1 comentarios:

Anónimo dijo...

Muy buena info

Publicar un comentario en la entrada