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.

No comments: