Showing posts with label Classfactory. Show all posts
Showing posts with label Classfactory. Show all posts

Tuesday, April 21, 2009

Interactive compilation and RAD tools with LayerD









PD: new SDK coming soon: classfactory debugging, several bug fixes, Java to Meta D++ compiler, basic manual about OO in Meta D++ (in Spanish), more samples.

PD2: I know, this is English... I don't have time to write both Spanish and English so I have to make a choice. ... In fact I don't have time to write in one single language :-P

Tuesday, June 24, 2008

Inferencia de tipos de variables locales

Meta D++ (el único lenguaje LayerD de alto nivel funcional hoy por hoy) basa su semántica en una copia exacta del lenguaje intermedio de la plataforma LayerD, el lenguaje Zoe.

Zoe posee características de orientación a objetos básica como polimorfismo, ocultación y herencia. Sin embargo, algunas características disponibles en otros lenguajes de programación orientados a objetos no se encuentran disponibles nativamente. Por otro lado, Zoe (y por tanto todo lenguaje LayerD de alto nivel como Meta D++) posee un diseño modular y procesamiento programable en "tiempo de compilación". Estas características de Zoe permite programar extensiones para los lenguajes LayerD de alto nivel sin depender de los fabricantes o diseñadores de lenguajes.

En esta nota mostraré cómo es posible incorporar en Zoe (y por extensión en Meta D++) inferencia de tipos para variables locales. Esta idea es desarrollada en algunos lenguajes como C# a partir de la versión 3.0 y trata simplemente de no requerir duplicar el tipo en una declaración "infiriéndolo" de su inicialización, por ejemplo en lugar de escribir:

int a = 12;
ArrayList lista = new ArrayList();
MiTipo[] matriz = new MiTipo[4];

En C# a partir de la versión 3.0 se puede escribir lo siguiente:

var a = 12;
var lista = new ArrayList();
var matriz = new MiTipo[4];

El lenguaje Meta D++ no soporta esta capacidad nativamente que en ocasiones suele ser útil permitiendo escribir unas cuantas letras menos a los programadores y también mejorar la legibilidad del código eliminando la redundancia. Pero ello no es limitación para incorporar la característica en Zoe usando una classfactory y luego poder usarla desde Meta D++.

El código en Meta D++ para incluir esta capacidad usando una classfactory es el siguiente:

public factory class var{
public:

//Declaro el constructor de tipo por defecto
type var(){
    try{
        //Si no es llamado como parte de una
        //declaracion de variable local es un error
01        if(context.get_Parent().get_TypeName()!="XplDeclarator")
            return new XplType();
        //Obtengo la declaración
02        XplDeclarator^ decla = (XplDeclarator^)context.get_Parent();
        //Busco la primera expresion de inicialización
03        if(decla.FindNode("/e")!=null){
            //Determino el tipo a partir de la expresion de inicialización
04            return ZoeHelper::MakeTypeFromString(    ((XplExpression^)decla.FindNode("/e")).get_typeStr() );
        }
        else{
            //Si no posee expresion de inicializacion
            //busco la primera asignación
05            XplNode^ temp = context.CurrentBlock.FindNode(
                        "/a/l/n("+decla.get_name()+")");
            if(temp!=null){
                //Determino el tipo a partir de la expresion
                //derecha de la primera asignación
06                return ZoeHelper::MakeTypeFromString( ((XplAssing^)temp).get_r().get_typeStr() );
            }
        }
    }
    catch(Exception^ error){
        //Si hubo algun error emito un mensaje y luego genero un error
07        Console::WriteLine(error.Message);
    }
    //Esto generará un error de compilación
08    return new XplType();
}

}

Efectivamente el código para agregar inferencia de tipos de variables locales lleva apenas 12 líneas de código efectivo en Meta D++. Y permite ser utilizado desde un programa cliente Meta D++, por ejemplo como sigue:

static int Main(string^[] args){
    ArrayList^ test = new ArrayList();
    ///Insert code here
    var n = "hola";
    var matriz = new int[] = {1,2,3,4};
    var matriz2 = new App[4];
    var matriz3;
    matriz3 = new int[10];
    var j;
    j = 12.4f;
    var y;
    y = new App();
    return 0;
}

El ejemplo de programa cliente inferirá que "n" es de tipo "string^", matriz de tipo "int[]", j de tipo "float", etc.

¿Cómo funciona la classfactory?

La classfactory "var" utiliza un tipo de constructores especiales accesibles por classfactorys. Estos constructores se denominan "Constructores de Tipo", específicamente el usado es un "constructor de tipo por defecto" ya que no toma ningún parámetro.

Los constructores de tipos en classfactorys son llamados cada vez que un programa cliente declara una variable o nombra el tipo de la classfactory, en el caso del ejemplo cada vez que se declara una variable local de tipo "var".

A continuación explico cada una de las líneas marcadas con números del programa:

01 – Si no se trata de una declaración de variable local retorno un tipo vacio (usando "new XplType()") lo cual generará un error en el programa cliente, en este caso es lo que deseo.
02 – Obtengo el nodo de declaración, es decir el padre del contexto de llamada al constructor de tipo.
03 – Busco la primera expresión a partir del nodo de declaración, la cual será la expresión de inicialización.
04 – Si encuentro una expresión de inicialización utilizo el tipo de dicha expresión para inferir el tipo de la variable.
05 – Si no encuentro una expresión de inicialización busco una asignación.
06 – Si no encuentro una asignación es un error, emito un tipo vacio para generar el error.
07 – Si ocurre un error inesperado cualquiera capturo la excepción.
08 – Si la ejecución llega a la última línea es un error, retorno un tipo vacio para generar el error.

Resumen

Para agregar una nueva funcionalidad, en este caso inferencia de tipo de variables locales, basta con desarrollar una classfactory de una decena de líneas. Luego, la funcionalidad esta disponible en cualquier lenguaje LayerD de alto nivel sin necesitar cambiar ningún compilador o modulo generador de código, simplemente código en el mismo lenguaje que el resto de un programa ordinario LayerD. Por supuesto que no todas las nuevas funciones que querramos agregar a los lenguajes LayerD serán tan fácil de desarrollar, sin embargo, si poseeremos en nuestras manos las herramientas y la libertad para hacerlo cuando creamos que lo necesitamos sin depender de los diseñadores de los lenguajes o los fabricantes de los compiladores.

El ejemplo sencillo presentado se puede mejorar emitiendo errores usando la interfaz "compiler" o incluso permitir la inferencia de variables locales sin utilizar el tipo "var". Más interesante, toda extensión semántica para el lenguaje, como la presentada, la podemos usar sólo donde lo queremos e incluso combinar funcionalidad.

Otros tipos de extensiones semánticas que podemos desarrollar para cualquier lenguaje LayerD, por nombrar algunas, incluyen: tipos genéricos, aspectos, tipos anónimos, precondiciones y postcondiciones, tareas, bloques para ejecución en paralelo, bloques for concurrentes para matrices o colecciones, estructuras de sincronización, bloques de recuperación, y un etcétera tan largo como tu propia imaginación y capacidad.

Monday, June 9, 2008

Un lenguaje de dominio especifico para interfaces gráficas

Muchas veces me paso que al desarrollar una aplicación de escritorio no incluí una ventana más para evitar el tedio de tener que abrir el diseñador de ventanas del entorno y genera otro archivo y otra clase más con todo el código generado (que en general es terrible je). Por tanto, para evitarme esa complicación termine reutilizando otra ventana que ya tenía aunque no fuera lo más adecuado.

Por otro lado, si hemos tenido la oportunidad de programar interfaces gráficas en más de un lenguaje o en más de una API aún en el mismo lenguaje, sabremos que cada quién trabaja las interfaces como quiere. En definitiva una ventana con un textbox y un botón aceptar, es eso. Sin embargo, si lo programamos en C# es de una forma con Windows Form, con WPF es de otra manera, si usamos C++ lo podemos hacer con el API de Windows, con MFC, con GTK o cualquier otra librería, en Java lógicamente es distinto, en Javascript también por supuesto es diferente. En cada plataforma los eventos se enlazan de manera diferente, los contenedores trabajan distinto, entre otros problemas.

El problema que encara el ejemplo del post, es la complejidad del código y la poca legibilidad. Por ejemplo, si construimos una ventana con C# usando Windows forms debemos de hacer una clase que herede de System.Windows.Forms.Form, luego estableceremos las propiedades en el constructor o en algún otro método. Los controles internos también los deberemos de definir en general como campos, agregarlos al contenedor, setearle las propiedades, capturar los eventos, etc. Todo este código hace que al final tengamos una clase la cual, si no fuera por la ayuda de los IDEs nos sería complicado de manipular y entender.

En lugar de tener que escribir todo ese código no necesariamente intuitivo para quien no uso antes la librería, en LayerD podemos desarrollar una classfactory que nos permita declarar y utilizar interfaces graficas de la siguiente forma:

GUI::Window(MiVentana3){

    Text = "Una ventana en un DSL con LayerD";

    Controls{

        Button(CSalir){

            Text="Salir"; AutoSize=true;

            Click{

                MessageBox::Show("Saliendo de la aplicación");

                this.Close();

            };

        };

    };

};

El código superior casi no necesita explicación si se toma el trabajo de leerlo. Declara una ventana cuyo nombre será "MiVentana3", establece el título de la ventana con "Text = "Una ventana en un DSL con LayerD"". Luego se especifican los controles directamente dentro del bloque "Controls". El ejemplo muestra un botón "CSalir" al cual se capturo el evento "Click" simplemente escribiendo "Click{…}" y proporcionando la implementación del evento entre llaves, las propiedades del botón se establecieron como simples asignaciones "Text = "Salir";" y "AutoSize = true;".

El lenguaje de dominio específico está pensado de tal forma que es posible utilizar cualquier clase de Windows Forms directamente dentro de la colección de controles, setear propiedades y capturar eventos de forma directa usando sólo el nombre del evento y proporcionando la implementación en un bloque.

En el SDK de LayerD podrá encontrar un ejemplo de uso de este lenguaje de dominio específico "EjemploGUI.dpp" y la classfactory que implementa el lenguaje de dominio especifico en "Zoe.DotNET.UtilsTemplate.dpp". Puede insertar un ejemplo en su código usando el modo interactivo con la instrucción "Zoe::DotNET::Utils::iGUI::Help()" (o sólo "iGUI::Help()" si ya tiene un using "Zoe::DotNET::Utils").

Si comparamos el código superior con el código de una ventana en C# o Java es muy probable que le parezca mucho más claro, más fácil de leer y hasta se anime a escribirlo a mano sin la ayuda de un diseñador :-) .

Por cierto, la classfactory que implementa este lenguaje de dominio específico no tiene miles de líneas de código :-). El ejemplo, encara uno de los problemas de las GUI en muchos de los lenguajes actuales volviendo el código de GUIs fácil de escribir, leer y mantener (lo cual no es necesariamente poco), pero sólo sirve para ser utilizada para desarrollar GUIs para .NET usando Windows Forms. Una mejora de este DSL será agregar la capacidad multiplataforma y permitir que sirva para Windows Forms, WPF, ASP.NET y Java. Ello es perfectamente posible en LayerD, simplemente hay que trabajar un poquito más :-), con seguridad uno de los ejemplos futuros.

A modo de avance les paso un ejemplo de un GUI similar para hacer paginas ASP.NET en LayerD:

///Pagina ASP.NET 2.0 implementada con un DSL

ASPNET::Page(TestPage){

    Title = "La primera pagina ASPNET en un DSL con LayerD";

    Controls{

    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";

    "<html xmlns=\"http://www.w3.org/1999/xhtml\" >";

    "<head>";

    "<title>Pagina ASP.NET 2.0 implementada con un DSL usando Meta D++.</title>";

    "</head>";

    "<body>";

    "<h2>El título de mi primera pagina ASP.NET en LayerD</h2>";

    "<br/>";

    HtmlForm(form1){

    Controls{

        Label(l2){

            CssClass="miClaseCSS";

            Text="Label Inicio";            

        };

        "<br/>";

        Calendar(c1){

            ID="c1";            

        };

        "<br/>";

        Button(CSalir){

            Text="¡Hacer Algo!";            

            Click{

                l2.Text="Hola Mundo desde el Server";

            };

        };

        "<br/>";

        "<br/>";

        Button(CCambiar){

            Text="Cambiar página";

            Click{

                Response.Redirect("testajax.aspx");

            };

        };

    };

    };

    "</body>";

    "</html>";

    };

};

///Fin Pagina ASP.NET 2.0 implementada con un DSL

Hasta la próxima!!

Friday, May 9, 2008

Inferencia de Esqueleto de Clases a partir de una Rutina Cliente de Ejemplo

Los binarios de LayerD ya estan disponibles para todo el mundo, por tanto comienzo a postear algunos ejemplos. El presente ejemplo es una classfactory un tanto avanzada ya que utiliza varias funciones del CodeDOM de LayerD. Sin embargo, muestra como con classfactorys relativamente simples es posible implementar herramientas que programarlas en IDEs nos llevarían con seguridad bastante más tiempo y sólo nos servirían para ese IDE y lenguaje en particular.

Para probar éste ejemplo baje la última actualización del SDK de LayerD desde SourceForge. El SDK de las semanas anteriores no funcionará.

Los fuentes para la classfactory y el cliente de ejemplo los puede bajar aquí.

Muchas metodologías de programación promueven la escritura en primer lugar de los tests y luego la implementación de las clases y demás artefactos. Entre estas metodologías encontramos las denominadas "Test Driven Development" - Desarrollo Guiado por Pruebas - y los métodos "Agile Programming" - Programación Agíl, o métodos ágiles -.
Muchas otras veces en la medida que utilizamos una clase que nosotros escribimos nos damos cuenta que nos faltaron definir métodos, propiedades, etc.

Si bien, muchos entornos integrados - IDEs - poseen herramientas que permiten escribir la estructura de por ejemplo métodos inexistentes su funcionamiento no siempre es completo o se adecua a lo que necesitamos. Por ejemplo, en Visual Studio 2005 y 2008 podemos pedirle al entorno que escriba el prototipo de un método por nosotros, sin embargo debemos realizar una serie de clics o controles de tecla por cada método faltante, adicionalmente no permite inferir otras construcciones faltantes en clases como propiedades o indexadores.
Por tanto, dependiendo del entorno que poseamos y las capacidades "RAD" de dicho IDE tendremos o no hasta cierto punto la funcionalidad necesaria para encarar de una manera productiva este tipo de enfoque "Guiado por Pruebas".

El ejemplo de classfactory interactiva siguiente permite dado un ejemplo de uso inferir gran parte de la estructura pública de una clase incluyendo métodos y propiedades.

Una parte del código de la classfactory interactiva es el siguiente



public interactive class TypeFromSample{
public:
static exp void New(iname void className){
XplClass^ clase = null;
// Search if provided className exists on current namespace
// If class doesn't exists create one, insert on current namespace and return a new call
// to current function
clase = (XplClass^)context.CurrentNamespace.FindNode("/Class[name='" + className.Identifier + "']");
if (clase == null){
clase = writecode{
public class $className{
}
};
// Insert the class on current namespace
context.CurrentNamespace.Children().InsertAtEnd(clase);
// Return a call to this function with same parameter
return writecode( TypeFromSample::New($className) );
}
// Search for al missing members in current function, find member access binary operator expressions
XplNodeList^ items = context.CurrentFunction.FindNodes("/bo[targetMember='?']");
// For each missing member try to infer it.
for (XplBinaryoperator^ op in items){
if (op.get_targetClass().Contains(className.Identifier)){
// Insert the inferred member on target class
clase.Children().InsertAtEnd(CreateFunctionFor(op));
}
}
// Return
return null;
}
...

El método principal es "New" el cual es estático y toma como argumento un "iname", los "inames" permiten tomar como argumento de funciones identificadores, en este caso cualquier identificador incluyendo un identificador no declarado (esto es por ser un "iname void") y retorna una expresión cualquiera.
Lo primero que se realiza es verificar si existe en el espacio de nombres actual una clase con el nombre indicado en el argumento, si no existe inyecta una nueva clase con dicho nombre y retorna una llamada al mismo método con el fin de que el compilador Zoe vuelva a realizar otro ciclo de compilación y al ejecutarse nuevamente la función "New" se posea información semántica parcial sobre la manera en la que el programador intento usar al tipo que queremos inferir su estructura.
Si la clase indicada como argumento ya existe el método procede a buscar todos los accesos a miembros no resueltos, y luego procesa de dichos elementos sólo los que se correspondan con un acceso al tipo de la clase indicada y los procesa uno por uno con la ayuda de la función "CreateFunctionFor".
La función "CreateFunctionFor" de la classfactory utiliza la información de los nodos del CodeDOM para determinar si la expresión padre del acceso a miembro se trata de una llamada a función o de una asignación. Si la expresión padre es una llamada a función infiere los parámetros y tipo de retorno de la función, si es una asignación infiere una propiedad para la clase.

Si el programa cliente de la classfactory es el siguiente:

static int Main(string^[] args){

// Llamanda a Classfactory Interactiva
// para crear el prototipo de una clase a
// partir de un ejemplo de uso
TypeFromSample::New(MyClass);

// Código de ejemplo de uso a partir
// del cual deseo armar el prototipo
// de MyClass
MyClass^ var = new MyClass();
var.Add(1, 2);
var.CurrentValue = "string";
var[2] = "test";
int n;
n = var.HashOf("hola");
n = var.foo(1, "chau", var, args);
return 0;
}

Al compilar el cliente en modo interactivo se inferirá la siguiente estructura para MyClass:

public class MyClass{
void Add(int p1, int p2){
throw new NotImplementedException();
}
string^ property CurrentValue {
get {
throw new NotImplementedException();
}
set {
throw new NotImplementedException();
}
}
int HashOf(string^ p1){
throw new NotImplementedException();
}
int foo(int p1, string^ p2, Sample::MyClass^ p3, string^[] p4){
throw new NotImplementedException();
}
}

La classfactory "TypeFromSample" puede mejorarse en muchas formas, por ejemplo:
  • Permitiendo inferir indexadores y constructores
  • Inferir todos los tipos faltantes (más de un tipo a la vez)
  • Presentar interfaz de usuario al programador para consultar sobre una desición de inferencia

La classfactory interactiva tiene menos de 100 líneas de código útil y permite implementar la inferencia de una clase de forma más completa que Visual Studio. Al tratarse de una classfactory interactiva en el futuro también podrá utilizarse para cualquier otro lenguaje LayerD de alto nivel que no sea Meta D++.

A diferencia de una herramienta en un entorno intergrado la classfactory funciona de forma completamente independiente de cualquier entorno e incluso de cualquier editor de texto por ser procesada por los compiladores LayerD directamente, por tanto podemos utilizar su funcionalidad aunque programemos con Notepad. También es posible adaptar su comportamiento a lo que nosotros necesitemos, por ejemplo agregando inferencia de constructores, interfaces, métodos de colección, eventos, etc. y el trabajo que realizamos en dicha utilidad no perderá vigencia porque salga un nuevo entorno de desarrollo.

Es preciso notar que en la actualidad no es posible desarrollar nada similar con ningún lenguaje main-stream sin utilizar el modelo de extensión de algún entorno en particular. En su lugar, LayerD permite desarrollar este tipo de herramientas típicas de entornos IDE directamente en el mismo lenguaje en el cual escribimos todos nuestros programas y usando el mismo conocimiento que poseemos del lenguaje y la programación de classfactorys en general sin atarnos a ningún entorno, versión, plataforma o fabricante en particular.