domingo, 25 de octubre de 2009

Transparencia en tu macro

Flashback

Vamos a volver atrás en el tiempo a la entrada de Higiene en tu macro. En un punto del texto decía...

Como ejemplo, una macro m definida así:

macro m :=
{
y=3; //Defino la y dentro del código.
f(z);
}


Y usada en un código así:

z=7;
m;
print(y); //Debería dar error, no he definido ninguna y en este código.

Da este resultado:

z=7;
y=3; //Contamino el entorno de fuera desde la macro.
f(z); //Uso la z de fuera en la macro ¿es esto deseable?.
print(y); //Captura la y de la macro en vez de estar sin definir.

Es completamente antihigiénico porque introducimos el símbolo y en el entorno de uso.

En aquella entrada nos dedicamos a la higiene. Es decir, que los símbolos de dentro de la macro no se escapasen fuera. Ahora vamos a pensar en la transparencia referencial: que los símbolos de fuera no me afecten dentro.

Transparencia referencial

Realmente la transparencia referencial es más que los símbolos de fuera no afecten dentro. Significa que nada de lo de afuera va a afectar la macro (igual con funciónes). Por tanto, la macro (o función) sólo podrá depender de los argumentos que se le pasen. Exactamente como una función matemática.

Para saber si una macro (o función) f() es transparente o no, hacemos el siguiente experimento mental:

  • Llamo a f() con un valor concreto. Por ejemplo, f(3). Obtengo un resultado R1.
  • Supongo que se ejecuta código arbitrario entre medias.
  • Llamo a f() con el mismo valor concreto. Por ejemplo, f(3). Obtengo ahora un resultado R2.
  • ¿Existe la posibilidad de que el resultado R1 sea distinto al resultado R2?

Si la respuesta es "sí", no es transparente.

En general, queremos transparencia porque nos permite razonar con mayor facilidad sobre lo que hace una macro (o función) El sistema de entornos expuesto en el post de la higiene es automáticamente transparente (gracias al entorno padre=entorno de definición). Ahora bien, ¿es esto deseable?

Deseable opacidad

Si no tenemos transparencia de ningún tipo, cualquier operación que hagamos dentro de una macro (o función) tendrá un significado desconocido. Supongamos

macro m := { y=x*x }

Parece que es hacer y el cuadrado de x. Ahora bien, podría darse el caso de que el uso de m apareciera en el siguiente entorno.

defino * como +

m //Uso la macro

¡Ahora y no es el cuadrado es el doble! Así pues, la opacidad referencial implica usar símbolos cuyo valor o definición puede realizarse luego. Esto es muy peligroso, pero puede ser muy útil.

Imaginemos el siguiente ejemplo donde defino la macro m con dos parámetros.

macro delay(os, ms) :=

{

 static_if(os==windows) Sleep(ms)

 else static_if(os==linux) usleep(ms*1000)

 else error;

}

Para empezar es una macro higiénica (ni el os ni el ms que defino en el entorno local podrán usarse fuera de la macro). Como es macro, se ejecuta en tiempo de compilación. Esto se resalta con el uso de static_if en vez de if a secas. El static_if no genera código, simplemente es como si no se escribiera el código en la rama que no se toma. En ese sentido es como el #if del preprocesador de C/C++.

Para continuar, supongamos transparencia referencial para la macro. Eso quiere decir que todos los símbolos han de tomarse del entorno de definición ¡y eso es un problema! Porque si estamos en Windows, usleep no estará definido y será un error y, si estamos en Linux, Sleep no se encontrará y será un error.

Es decir, que esta macro necesita ser opaca. Pero sólo con con los símbolos Sleep y usleep. No con el símbolo static_if ni con el * que queremos que estén vinculados con los valores del entorno de definición.

Resumiendo

En general queremos proteger los símbolos. Una macro debe ser higiénica y transparente por defecto.

Si queremos definir un símbolo en el entorno de llamada, querremos que un símbolo sea no-higiénico. Usualmente este símbolo deberá ser pasado por argumento para controlar qué se ensucia en el entorno de llamada.

Si queremos usar un símbolo del entorno de llamada, querremos que un símbolo sea opaco. Usualmente este símbolo no deberá ser pasado por argumento porque es posible que ni siquiera exista (caso Windows/Linux).

0 comentarios:

Publicar un comentario