miércoles, enero 09, 2008

Clases anidadas dentro de interfaces

C# no lo permite, pero el CLR no pone objeciones:
.class public interface abstract auto ansi MyInt
{
.class abstract auto ansi sealed nested public MyExt
extends [mscorlib]System.Object
{
}
}
El listado anterior muestra una clase estática anidada dentro de un tipo de interfaz. Naturalmente, para poder crear este "engendro" he tenido que usar el API de reflexión, porque ninguno de los lenguajes actuales acepta un tipo anidado dentro de una interfaz.
¿Para qué necesito esto? Resulta que es la forma más elegante de definir métodos de extensión para un tipo de interfaz:
IStack = interface[X]
IsEmpty: Boolean;
Top: X;
method Push(Value: X);
method Pop;
method Clear;
begin
while not
IsEmpty do Pop;
end;
end;
El truco, por supuesto, consistiría en generar una clase estática adicional, con métodos de extensión. El problema es más bien estético: la proliferación de clases con nombres extraños en el espacio de nombres del programador (el mecanismo usado por C# y compañía para "activar" las extensiones es un desastre).
La solución puede ser anidar la clase con las extensiones en el tipo de interfaz. Primera dificultad: ya hemos visto que está prohibido en los compiladores, pero no en el runtime, de Microsoft. Segunda dificultad: resulta que C# no permite tampoco métodos de extensión dentro de una clase anidada. Esto va a complicar la compatibilidad de los métodos generados en Freya con este mecanismo: lo más que se puede hacer es ofrecer un switch de compatibilidad. Si está inactivo, porque la aplicación o el ensamblado van a ser consumidos desde Freya, los métodos de extensión asociados directamente a interfaces se generarán en una clase anidada. En caso contrario, se utilizará el horroroso sistema actualmente empleado por Microsoft.
Ocurre que existen motivos adicionales para investigar en esta dirección: Freya permite definir aserciones en un tipo de interfaz (la última vez que eché un vistazo a Chrome, éste no lo permitía). Si un tipo de interfaz representa la definición de un "contrato", ¿qué hay más natural que permitir reglas que precisen los términos de dicho contrato? Por desgracia, precondiciones y postcondiciones generan código, y de momento, Freya estaba generando ese código en clases auxiliares. Con el nuevo mecanismo, podremos encapsular este código en clases anidadas, y despejar un poco el espacio de diseño.

... y aprovecho para aclarar un poco la relación entre aserciones, interfaces y métodos de extensión. Supongamos un caso muy sencillo de interfaz con una precondición:
IStack = interface[X]
property IsEmpty: Boolean;
method Pop;
requires not IsEmpty;
// ...
end;
Ahora mismo, Freya genera una clase estática con métodos para cada aserción de la interfaz. Como se trata de miembros de una interfaz, necesariamente públicos, no hay problema con los niveles de acceso. Con la nueva idea de implementaciones en interfaces (formalmente, se puede hablar de traits o mixins, aunque no es exactamente lo mismo), lo que haríamos ahora sería equivalente a generar un método "de extensión" en la interfaz:
IStack = interface[X]
property IsEmpty: Boolean;
method Pop;
// ...
method Pop$Pre;
begin
if
Self.IsEmpty then
raise new Exception;
end;
end;
  1. En realidad, Pop$Pre sería un método estático dentro de una clase estática anidada, que recibiría un puntero de tipo IStack[X].
  2. Observe el truco del nombre: como el dólar no es aceptado dentro de un identificador, el programador no tendría acceso directo al método. Es un truco bastante usado, incluso por el compilador de C#.
  3. Toda clase que implementase IStack[X], siempre que se programase en Freya, añadiría automáticamente una precondición a su implementación de Pop, que ejecutaría el método Pop$Pre sobre el objeto activo convertido en el tipo de interfaz.
¿La ventaja de anidar la clase auxiliar dentro de la interfaz? Hasta ahora, Freya generaba una clase interna independiente... y tenía que cumplir con un estricto protocolo de asignaciones de nombres para detectar estas clases al leer un ensamblado generado en otro proyecto Freya. Con esta idea, por el contrario, la asociación entre la interfaz y la clase auxiliar es inmediata.

Etiquetas: , , ,

2 Comments:

Blogger Pablo Grisafi said...

Bueno...solo para molestar...aclaro que usar una clase anidada dentro de una interface se puede... en JAVA!

Lo hicieron, creo, para poder hacer "falsos mixins", es decir, compartir interfaz y al menos parte de la implementacion sin usar herencia

miércoles, febrero 06, 2008 8:02:00 p. m.  
Blogger Ian Marteens said...

Si es un buen recurso, da igual si lo ofrece Java o el porquero de Agamenón. Tengo incluso la impresión de que el CLR permite ese anidamiento para poder implementar J#. La asignación covariante de vectores de referencias también existe en el CLR para el soporte de Java.

:) Es difícil que en algo, todo sea malo.

lunes, febrero 11, 2008 8:40:00 p. m.  

Publicar un comentario

<< Home