domingo, 27 de febrero de 2011

Minipost para el ahorro de energía

Simplificando mucho.

¿Se apaga la CPU? ¿Se apaga la RAM? ¿Se cierra la sesión?
Encendido No No No
Suspendido (Modo S3) No No
Hibernado (Modo S4) No
Apagado

Nota: Para los modos ver ACPI.

miércoles, 23 de febrero de 2011

miniSL parte 10 - Extracción de valores

Si imprimir los valores de una celda fue fácil, obtenerlos va a ser aún más fácil. Podríamos leer directamente los campos de una celda, pero es más conveniente encapsular las operaciones más comunes en una única función miembro de CELL que lance una excepción si algo va mal.
int GetInteger()const
{
 if(type==INT_LIT)
  return int_val;
 throw L"Integer expected";
}

bool GetBoolean()const
{
 if(type==BOOL_VAL)
  return bool_val;
 throw L"Boolean expected";
}

STRING const& GetString()const
{
 if(type==STRING_LIT)
  return *string_val;
 throw L"String expected";
}

El único caso no trivial aparece cuando queremos explorar o modificar los entornos. Recordemos que cada celda de entorno tiene una tabla de símbolos (un ENVIR_TABLE que era un typedef de std::map<CELL*, CELL*&gt;) en el campo envir_table y un entorno padre en el campo parent_envir. Estas complejidades son necesarias para código con una forma similar a esta:
> define(a, 5)
[]
> define(f, lambda([x],[x+a]))
[]
> f(3)
8

Cuando se definen a y f, se hace en el entorno global. Cuando se llama a f(3) se crea un nuevo entorno local donde x se vincula a 3. ¿Pero cuánto vale a en el entorno local? ¡No está definida en el entorno local, está definida en el entorno global! La solución es crear un enlace, el del entorno padre, de la forma indicada en el diagrama de abajo.



De esta manera están disponibles tanto la a como la propia f dentro del cuerpo de la f. De todo esto, lo único que necesitamos por ahora son dos funciones en la clase Script. La primera hace la búsqueda de un nombre que se realiza siempre de la misma forma. Primero, buscamos en el entorno actual y, si no se encuentra, repetimos recursivamente buscando en el entorno padre.
CELL* Script::FindName(CELL& name, CELL& envir)
{
 //Try to find the name in current environment
 ENVIR_TABLE::const_iterator i=envir.envir_table->find(&name);
 if(i!=envir.envir_table->end())
  return i->second;

 //Failed, try parent
 if(envir.parent_envir!=NULL)
  return FindName(name, *envir.parent_envir);

 //Failed also, not found
 return NULL;
}

La segunda, para cambiar un nombre, hace casi lo mismo: buscamos el nombre en el entorno actual cambiándolo si lo encontramos y, si no lo encontramos, llamamos recursivamente la función en el entorno padre. Si no hay entorno padre es que el nombre no estaba definido.
void Script::ChangeName(CELL& name, CELL& envir, CELL& value)
{
 //Try to find the name in current environment
 ENVIR_TABLE::iterator i=envir.envir_table->find(&name);
 if(i!=envir.envir_table->end())
 {
  (*envir.envir_table)[&name]=&value;
  return;
 }

 //Failed, try parent
 if(envir.parent_envir!=NULL)
  return ChangeName(name, *envir.parent_envir, value);

 //Failed also, not found
 throw L"Name to be changed is undefined";
}

También necesitaremos la operación de definición de un nombre en el entorno actual como hace define, sin buscar en los entornos padre, pero coincide exactamente con la operación básica de inserción en std::map así que, aunque sea algo incorrecto al romper la abstracción sobre la implementación, usaremos directamente la clase std::map.

Hasta ahora sólo hemos explicado cómo leer una celda. En la siguiente entrada hablaremos de la creación de celdas.

martes, 15 de febrero de 2011

Purgando conocimiento

Leo en Reddit que los administradores de Wikipedia se están dedicando a hacer purga de los lenguajes de programación menos conocidos como Nemerle (una maravilla de lenguaje basado en macros), Factor (de hecho leo el blog de su creador), Alice ML y otros.

Me causa una gran pena porque conocí estos lenguajes (y otros más) gracias a la Wikipedia. Quizás haya que ser de una universidad americana para que te tengan en cuenta.

Edit: Parece que Factor aún no ha caído. Los otros dos están borrados ya.
Edit2: 19-02-2011 Nemerle ha vuelto a aparecer, pero Alice ML sigue borrado.

jueves, 10 de febrero de 2011

¿Especificación de requisitos o especificación de requerimientos?

Otro caso en el que usar Google no nos ayuda a discernir cuál está bien o cual está mal. Si usamos "requerimientos" obtenemos más o menos las mismas páginas que si usamos "requisitos".

Por otra parte, el diccionario lo dice claramente: "requisito" es la circunstancia que se debe dar mientras que "requerimiento" es la acción de requerir algo (es sinónimo de "petición", "demanda", etc.)

Resumiendo:
  • "Especificación de requisitos" es la especificación de las circunstancias que se deben dar (para que el programa sea correcto).
  • "Especificación de requerimientos" sería la especificación de las peticiones o demandas. Obviamente, esta es la que se usa mal.

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.

miércoles, 2 de febrero de 2011

miniSL parte 9 - Imprimiendo celdas

La impresión de celdas en miniSL es relativamente sencilla usando un algoritmo recursivo. Según el tipo de celda, imprimimos su contenido tal y como se describió en la quinta parte de esta serie.

void CELL::Print(OSTREAM& o)const
{
 switch(type)
 {
 default:  o << L"{{**UNKNOWN**}}"; break;
 case UNUSED: o << L"{{**UNUSED**}}"; break;
 case INT_LIT: o << std::dec << int_val; break;
 case BOOL_VAL: o << (bool_val ? L"true" : L"false"); break;
 case LAMBDA_VAL:o << L"{{ "; code->Print(o); o << L" }}"; break;
 case NATIVE_VAL:o << L"{{NATIVE}}"; break;

Hasta aquí ningún extraño aparte de usar una llamada recursiva para imprimir el contenido del código en las funciones lambda. Para imprimir las cadenas debemos tener cuidado ya que pueden contener caracteres de control que no son imprimibles y hay que escaparlos.

case STRING_LIT:PrintEscapedString(o, *string_val); break;
 case NAME_CODE: o << L"@"; PrintEscapedString(o, *string_val); break;

Es importante notar que los nombres no son únicamente cadenas. Las cadenas se evalúan a sí mismas mientras que los nombres se evalúan buscándolos en el entorno actual. Para distinguirlos bien, pondremos el prefijo @ delante de ellos. De esta manera permitimos usar nombres con espacios o símbolos en ellos. Veremos que será muy útil para extender la sintaxis.

La impresión de las listas la vamos a hacer en la función CELL::PrintList() para así poder reutilizar el código a la hora de imprimir celdas COMBINE_CODE.

case CONS_CTOR: case EMPTY_LIT:
  o << L"[";
  PrintList(o, L", ");
  o << L"]";
  break;
 case COMBINE_CODE:
  op->Print(o);
  o << L"(";
  operands->PrintList(o, L", ");
  o << L")";
  break;

Finalmente, la impresión de un entorno se realiza iterando sobre él.

case ENVIR_VAL: o << L"{{ENVIR ";
  for(ENVIR_TABLE::const_iterator i=envir_table->begin(); i!=envir_table->end(); ++i)
  {
   o << *i->first->string_val << L"= ";
   i->second->Print(o);
  }
  o << L"}}";
  break;
 }
}

Nos queda por implementar entonces, la impresión de listas y de cadenas escapadas. Ambas funciones son relativamente directas.

void CELL::PrintList(OSTREAM& o, STRING const& separator)const
{
 for(CELL const* c=this; c->type==CONS_CTOR; c=c->tail)
 {
  if(c!=this)
   o << separator;
  c->head->Print(o);
 }
}

void CELL::PrintEscapedString(OSTREAM& o, STRING const& s)
{
 o << L"\"";
 for(STRING::const_iterator i=s.begin(); i!=s.end(); ++i)
 {
  if(*i<32) o << L"\\x" << std::hex << (unsigned int)(unsigned)*i << L";";
  else  o << *i;
 }
 o << L"\"";
}

Por ejemplo, la impresión de una lista, una función lambda y un booleano se ve así:

[1, 2, 3]
{{ [[@"x"], [@"+"(@"x", @"x")]] }}
true

Se observa que tanto la lista como el booleano es directamente código, pero la función lambda es un valor que no es representable directamente en código. Por eso lo vamos a escribir entre dobles llaves. Estos valores deben obtenerse como el resultado de una llamada. De hecho, también se pueden imprimir celdas del tipo COMBINE_CODE que son precisamente esas llamadas. Así, f(x,y) se imprimiría así:

@"f"(@"x", @"y")

De esta manera podemos ver los valores y el código que tenemos en el montículo, pero no nos basta con verlos. Debemos también poder utilizarlos. Para eso vamos a introducir una serie de funciones que nos permita extraer valores. Estas funciones van a ser también muy sencillas. Las dejaremos para la siguiente entrada.