miércoles, mayo 09, 2007

Object triggers

Aunque es prematuro, todo sea por concretar un poco la idea:
// Freya

Editor = class
public
property
CurrentPosition: Position;
event PositionChanged: EventHandler;
protected
method
OnPositionChanged; virtual;
begin
var
eh := PositionChanged;
if eh <> nil then
eh(Self, EventArgs.Empty);
end;
implementation
    // Este es un trigger: se admite read, write y execute.
// Pueden combinarse varios recursos en un mismo trigger.

on CurrentPosition(write)
var
// Estas variables se convierten en campos
flag: Integer := 0;
begin
flag++;
// Esto sí es una verdadera variable local:
var initialPosition := currentPosition;
finally
flag--;
if flag = 0 then
if initialPosition <> currentPosition then
OnPositionChanged;
end;
end;
A trigger, what else?He intentado no meter palabras reservadas nuevas, al menos mientras no estén más claras las ideas. Lo que quiero averiguar es si con esta simple técnica se pueden lograr todas las cosas positivas de la AOP, sin provocar, a la misma vez, todos los problemas que ahora provoca. La clase, por supuesto, tendría mucho más código, y los triggers, por llamarlos de algún modo, siempre van en la implementación de la clase (no interesan al cliente de la clase).
Importante: los campos usados por un trigger son solamente visibles para el propio trigger. Por ello puede usarse un nombre como flag sin preocupaciones, porque de existir ya un miembro con este nombre, el campo se renombraría.
Y por supuesto, la historia puede complicarse todo lo que se quiera: ¿se podrían heredar triggers para aplicarse a nuevos recursos? Probablemente sí, en la medida en que el recurso protegido pueda ser modificado en una clase derivada. ¿Tiene sentido definir triggers independientemente del recurso que protegen, para enlazarlos luego con un recurso en una implementación? Supongo que dependerá de la complejidad del código que pueda meterse en un trigger.
Observe también que, en general, podría vigilarse más de un recurso con el mismo trigger. Imagine que, en vez de tener una variable (o una propiedad) que encapsula fila y columna, tenemos dos campos separados. En tal caso, el mismo trigger se "tejería" para los métodos que modificasen uno u otro campo. ¿Tiene sentido definir triggers para que se apliquen cuando un método acceda a uno y otro campo? No lo sé: tengo que encontrar un ejemplo con sentido. De esto, de encontrar más ejemplos, se trata ahora.

NOTAS
A quien haya seguido ejemplos anteriores en Freya, le extrañará probablemente la implementación en línea del método OnPositionChanged. No hay nada extraño: Freya sigue manteniendo el viejo formato, con las declaraciones en secciones public/private y secciones implementation for para las implementaciones. Pero ahora también es posible utilizar el estilo en línea, como en Eiffel (y Java y C#). Observe, de paso, que incluso en tal caso sigue existiendo una sección implementation, aunque en ella ya no se menciona la clase, por ser innecesario. En esta sección siguen residiendo los "detalles de implementación", que sería inapropiado situar en una sección "regular". Por ejemplo: constructores de clase (estáticos), delegaciones de implementación para tipos de interfaz, implementaciones explícitas de miembros de interfaces y destructores.
Una vez aclarado este punto, observe que CurrentPosition es una propiedad sin implementación explícita. En estos casos, el compilador declara un identificador oculto y genera automáticamente los métodos de acceso necesarios. Mejor aún: cuando se hace referencia a la propiedad dentro de la misma clase, ésta se sustituye por una referencia al campo. Y si la propiedad no es virtual, el campo oculto se declara internal, y la optimización se realiza también para los usos de la propiedad dentro del propio ensamblado, con independencia de la clase. Si alguna vez se cuestiona la necesidad de Freya, recuerde que la calidad del código generado es muy superior a la media de los compiladores para .NET, a pesar de las limitaciones impuestas por la plataforma :)
Otro pequeño detalle: observo cómo copio el valor del evento PositionChanged antes de comprobar su valor y ejecutarlo. Es común ver por ahí código que no utiliza una variable temporal, pero aunque teóricamente es correcto, podemos tener problema si alguna vez accedemos al mismo objeto desde dos hilos paralelos. La técnica mostrada es la "correcta", teniendo en cuenta esta posibilidad.

Etiquetas: , ,

viernes, mayo 04, 2007

¿Por qué triunfa un lenguaje?

Es evidente que un lenguaje no siempre triunfa exclusivamente por sus méritos propios. De lo contrario, ¿cómo explicaríamos el larguísimo reinado de C++ y el injusto olvido de Eiffel?
En ocasiones, sin embargo, las causas del triunfo pueden ser sorprendentes, y puede que no hayan sido previstas por su autor. Lo digo porque acabo de recordar algo muy importante que influyó en mi decisión de usar Delphi como herramienta principal de programación... a pesar de conocer bien C++ y del estado más avanzado de este lenguaje y sus implementaciones por aquel entonces. No olvide que, a pesar de los méritos innegables de los genéricos de .NET, C++ tiene templates desde tiempo inmemoriales, igual que Eiffel (y no digamos ya Ada).
Me voy a callar esta causa (hay muchas más, qué duda cabe) un tiempo. Si quiere contar la suya en los comentarios, las iré añadiendo a este post (para ser más concisos, utilice negritas, si quiere, para destacar la parte que quiere que copie). Y luego diré la mía, si nadie la menciona antes...

Etiquetas: , ,

Una vieja buena idea

¿Cómo se escribe "un millón" en su lenguaje de programación favorito? En Freya, se escribe así:
1_000_000
La idea, naturalmente, no es nueva: creo que en Ada, como mínimo, hay algo parecido. Y en todo caso, la he copiado directamente de Eiffel. Sin embargo, ¿a que es útil?

Etiquetas: ,

jueves, mayo 03, 2007

Internal affaires

¿Y si llevásemos la propuesta de exportación selectiva aún más lejos? Quiero decir, ¿y si permitiésemos la mención de rutinas, propiedades y eventos en la lista de entidades con acceso a un recurso? Actualmente, en la sintaxis de Freya, la exportación selectiva tiene este aspecto:
MiClase = class
internal(OtraClase, YOtraMas)
Campo: Integer;
// ... etcétera ...
end;
Lo que ahora digo es esto otro:
MiClase = class
internal(MiClase.Propiedad)
Campo: Integer;
// ... etcétera ...
end;
Sí, este es un ejemplo "extremo": sólo permito que Campo sea usado por el código de la propiedad Propiedad... de la misma clase donde se ha definido Campo. En circunstancias normales, no habría que caer en el "tremendismo", pero esto permite usar una técnica que he echado de menos en ocasiones. Por ejemplo, en el editor de código se define un campo privado para almacenar la posición del curso. En paralelo, se usa una propiedad para encapsular esta posición. El caso es que, cuando se cambia la línea activa, se producen cambios en el búfer de líneas interno. Sin embargo, es relativamente seguro modificar directamente la columna activa... y uno, que es un triste pecador, lo ha hecho en varias ocasiones. De haber tenido la disciplina suficiente, podría haber asociado un evento a las escrituras en la propiedad. Pero, ¿quién localiza, a estas alturas, todas las violaciones de esta "regla"? Con la posibilidad de reducir el uso de un campo a una propiedad, como propongo, sería muy sencillo, no ya cumplir con la regla, sino ponerla inmediatamente en conocimiento de quien lee el código.
Resumamos las bondades de mi propuesta:
  • Se reduce fulminantemente la cantidad absoluta de dependencias potenciales entre clases, e incluso dentro de la misma clase.
  • Como esta técnica afecta solamente a las declaraciones internas, no hay peligro alguno de interacción con otros lenguajes .NET. Puedo seguir utilizando desde C# un ensamblado escrito en Freya, y viceversa.
  • Cuando en la lista de "entidades" a las que se le permite el acceso aparece una clase, se entiende que es lo mismo que "Clase.*": se le concede acceso a todos los miembros de la clase mencionada.
  • Este tipo de restricciones de acceso pueden interpretarse también como una modalidad de contrato ligado a la implementación de un recurso. Este tipo de contrato ya es muy popular entre los programadores, pero no existe una forma sencilla de hacerlo explícito.

La visión reduccionista

Una reflexión al margen: otra solución al evento que falta en mi editor de código podría utilizar la Programación Orientada a Aspectos: podría indicar que, para todos los métodos que modifiquen la posición del cursor, se genere un prólogo en el que se memorice la posición inicial, y un epílogo que dispare el evento si se han producido cambios. Menciono esto porque es importante observar la gran diferencia entre la solución OOP y la solución AOP. Y la observación tiene que ver con el énfasis que pone la AOP en el texto del programa. La visión OOP monta capas y más capas de abstracciones sobre las tiras de caracteres del programa, mientras que la visión AOP, al centrarse en el texto, se podría calificar incluso de reduccionista.
Y no se trata de un mero florilegio retórico. Por una parte, está demostrado que la AOP resuelve algunos problemas (no todos) de manera más eficiente, sencilla y controlable que la OOP. Pero se trata de sustituir una cosa por la otra... porque no es ese el propósito de la AOP. Se trata de que este enfoque reduccionista puede aportar claridad a muchos debates viciados. Durante tiempo hemos intentado juzgar la OOP según sus propios reglas. ¿El resultado? Pues que cada vez es más difícil evaluar los méritos de las propuestas y novedades. Y por otra parte, vemos a autores a los que se le supone cierta seriedad defendiendo tonterías como el llamado "duck typing".
¿Una pista sobre el impacto que puede tener la visión "reduccionista" en Informática? Ahí tiene el revuelo que han traido consigo las clases parciales de .NET 2.0. En principio, se trataría de un mero "truco" de ficheros... pero resulta que el "truco" ha encontrado aplicaciones de todo tipo. Y es que, al fin y al cabo, una de las grandes justificaciones de la OOP tiene que ver con la "mera" distribución del código fuente: en la programación estructurada original, se tendería a organizar el código de acuerdo a su función, mientras que en la OOP, el código se agruparía respecto al "propietario" de cada método. Eso facilitaría la adición de nueva funcionalidad: observe que se trata de una justificación textual y reduccionista.
Bienvenido sea el reduccionismo...

Etiquetas: , ,