NameBlock
El tamaño de los programas en Tol ha estado creciendo durante los últimos años, y al mismo ritmo que ha aumentado el número de lineas de código, han proliferado también el número de funciones y variables, casi todas ellas en el ámbito global. El resultado de este crecimiento incontrolado es un concurrido ámbito global que complica el nombrado de los elementos de Tol y disminuye la organización de los programas.
Con el objetivo de evitar esta tendencia se ha desarrollado en Tol un nuevo tipo de datos NameBlock que dé las funcionalidades típicas de los espacios de nombres (namespace) como el de C++, por ejemplo, aunque con ciertas diferencias pues no se trata tan sólo de un ámbito de nombres sino también de un contenedor de objetos que pueden ser privados ó públicos.
Esta característica está disponible desde la versión 1.1.6, aunque se presenta aún como sistema en pruebas y no es definitivamente oficial hasta la 1.1.7, momento en el que se recomienda su uso intensivo.
Definiciones
- Un bloque de nombres o NameBlock es un tipo de datos especial que se crea partiendo de un conjunto al que llamaremos base del NameBlock, y que implica un ámbito de localización de objetos.
- A sus elementos les llamaremos miembros y pueden ser tanto variables como funciones o estructuras.
- Los habrá que permanezcan ocultos fuera del ámbito y les llamaremos miembros privados.
- El resto, llamados miembros públicos, podrán ser accesibles desde el exterior, a pesar de ser siempre todos locales por construcción, sin necesidad de explicitar las llaves {...}.
sintaxis
Debido a que no deja de haber ciertas semejanzas con el tipo de datos Set, como contenedor de objetos que es, existirá la tentación de usar un NameBlock como si fuera un conjunto, pero hay que tener en cuenta que existen grandes diferencias, como se puede observar a continuación.
Reglas sintácticas
1. Reglas de declaración de un NameBlock
- 1.1. La sintaxis de definición de un NameBlock admite únicamente tres posibles formas BNF, de las cuales se dan más detalles en los apartados posteriores:
- 1.1.1.A partir de una expresión similar a la de un conjunto definido directamente con ...? :
<nameblock_declaration> ::= "NameBlock" <variable_id> "=" "[[" [{ <public_member_expression> | <private_member_expression> "," | ";" }] <public_member_expression> [{ "," | ";" <public_member_expression> | <private_member_expression> } ] "]]"
- 1.1.2.Como alias de otro NameBlock
<nameblock_declaration> ::= "NameBlock" <variable_id> "=" <member_expression> <member_expression> ::= <variable_id> | <variable_id>"::"<public_member_expression>
- 1.1.3.Como resultado de una función que devuelva NameBlock anidado
<nameblock_declaration> ::= "NameBlock" <variable_id> "=" <member_expression> <member_expression> ::= <variable_id> | <variable_id>"::"<public_member_expression>
- 1.1.1.A partir de una expresión similar a la de un conjunto definido directamente con ...? :
- 1.2. Declaración de miembros:
- 1.2.1 El prefijo subrayador simple _ especifica los miembros privados de un NameBlock.
<private_member_expression> ::= <type_id> "_"<alphanumeric_char><variable_id> ["=" <object_expression>]
- 1.2.2 El prefijo subrayador+punto _. se reserva para miembros de sólo lectura como por ejemplo _.autodoc.description. Estos miembros no son plenamente accesibles, por lo que son realmente un caso particular de miembro privado. Sin embargo, sí se puede obtener una copia de su contenido a través del operador :: lo cual impide su modificación por medios externos al NameBlock.
<read-only_member_expression> ::= <type_id> "_"<alphanumeric_char><.variable_id> ["=" <object_expression>]
- 1.2.3. Un NameBlock puede o no tener miembros privados pero ha de tener al menos un miembro público, con excepción del NameBlock Unknown del sistema que es el vacío.
<public_member_expression> ::= <type_id> <alphanumeric_char><variable_id> ["=" <object_expression>]
Ejemplo de uso inválido:NameBlock BadNS_1.2.3.a = Unknown; NameBlock BadNS_1.2.3.b = [[Real _a=0]];
Veámoslo con un ejemplo:NameBlock MyNS_1.2 = [[ Text _.autodoc.description = "NameBlock de ejemplo del punto 1.2"; //Public members Struct mystr { Real x_; Text t_ }; Set real2mystr(Real x) { mystr(x,""+FormatReal(x,"%.15lg")) }; //Private member Real _fun_aux(Real x) { x*4 }; //Public members Real fun_a(Real x) { _fun_aux(x)+1 }; Real fun_b(Real x) { _fun_aux(x)-1 }; //Private members Real _aux_1 = 1; Real _aux_2 = 2; Set _aux_3 = real2mystr(2); //Read only members Real _.exported_1 = _aux_1+_aux_2; Real _.exported_2 = _aux_1-_aux_2*_aux_3->x_ ]];
- 1.2.1 El prefijo subrayador simple _ especifica los miembros privados de un NameBlock.
- 1.3. Restricciones sobre la creación de miembros
- 1.3.1 Un NameBlock no puede tener varios miembros con el mismo nombre. Ejemplo de uso inválido:
NameBlock BadNS_1.3.1 = [[ Set _a = { [[Real x=0]] }; Set _b = { [[Real x=1]] }; _a[1], _b[1] ]]
- 1.3.2. Un NameBlock no puede tener miembros sin nombre. Ejemplo de uso inválido:
NameBlock BadNS_1.3.2 = [[Real 0]];
- 1.3.4. Un NameBlock no puede tener sólo sentencias que no devuelvan una variable, una función o una estructura, como por ejemplo el Write, o el While. Ejemplo de uso inválido:
NameBlock BadNS_1.3.4 = [[ WriteLn("Hola") ]];
- 1.3.5. Un NameBlock no puede tener miembros que sean globales o accesibles al mismo nivel que el propio NameBlock o a nivel superior. Ejemplo de uso inválido:
Real x=0; NameBlock BadNS_1.3.5 = [[Real x]];
- 1.3.1 Un NameBlock no puede tener varios miembros con el mismo nombre. Ejemplo de uso inválido:
2. Reglas de acceso a miembros
- 2.1. Para acceder a los miembros públicos se usará el nombre del NameBlock con el operador especial
Anything nameblock_id::public_member_id
Siguiendo con el ejemplo del punto 1.2 anterior:
Real MyNS_1.2::_.exported_1; Real MyNS_1.2::_.exported_2; Real MyNS_1.2::fun_a(MyNS_1.2::_.exported_1); Set table = [[ MyNS_1.2::mystr(1,"uno"), MyNS_1.2::real2mystr(4.0), MyNS_1.2::real2mystr(Pi) ]];
- 2.2. Evidentemente no se puede acceder con :: a los miembros privados. Ejemplo de uso inválido:
Real MyNS_1.2::_aux_1; Real MyNS_1.2::_fun_aux(1);
- 2.3. En ningún caso es posible acceder a los miembros públicos ni privados de un NameBlock mediante los operadores de acceso a Set Set[Real], Set[Text],y aún menos con Set->field_name puesto que no puede existir un NameBlock con estructura. Ejemplo de uso inválido:
Real MyNS_1.2[3]; Real MyNS_1.2["exported_2"]; Real BadNS_2.3->x_;
- 2.4. Nótese que el concepto de miembro se extiende tan sólo a los elementos a primer nivel del conjunto base de un NameBlock, y no al resto de elementos de su jerarquía recursiva de subconjuntos aunque tengan nombre accesible desde el nivel del conjunto.
Ejemplo de uso inválido:
NameBlock MyNS_2.4 = [[ Set a = [[ Real a1 = 1; Real a2 = 2 ]] ]]; Real MyNS_2.4::a1;
- 2.3. En ningún caso es posible acceder a los miembros públicos ni privados de un NameBlock mediante los operadores de acceso a Set Set[Real], Set[Text],y aún menos con Set->field_name puesto que no puede existir un NameBlock con estructura. Ejemplo de uso inválido:
3. Reglas de anidación
- 3.1. Para poder acceder de modo recursivo a los NameBlock's, estos pueden estar anidados como en este nuevo. Ejemplo:
NameBlock MyNS_3.1 = [[ NameBlock a = [[ Real a1 = 1; Real a2 = 2 ]] ]]; Real MyNS_3.1::a::a1;
- 3.2. Un modo especialmente práctico que puede ayudar a la organización del código sería usar el Include como en el siguiente ejemplo 3.2
//Fichero ns1.tol ... NameBlock NS1 = [[ Real _aux_1 = 1; Real _aux_2 = 2; Real a = _aux_1+_aux_2; Real b = _aux_1-_aux_2 ]]; ... //Fichero ns2.tol ... NameBlock NS2 = [[ Real _fun_aux(Real x) { x*4 }; Real fun_a(Real x) { _fun_aux(x)+1 }; Real fun_b(Real x) { _fun_aux(x)-1 } ]]; ... //Fichero MyNS_3.2.tol NameBlock MyNS_3.2 = [[ //Private: Set _ns1 = Include("ns1.tol"); Set _ns2 = Include("ns2.tol"); //Public: NameBlock NS1; NameBlock NS2 ]];
Obsérvese que sólo se publican NS1y NS2 por lo que cualquier cosa definida en ns1.tol y ns2.tol distinta de ellos permanecerá inaccesible externamente. - 3.3. Para mayor comodidad se puede renombrar externamente un NameBlock anidado con un alias identificativo más corto que la concatenación completa de NameBlock's, como se ha visto en el punto 1.1.3. Esto evita tener que escribir demasiados nombres largos que dificulten la lectura. Pero se debe usar siempre de forma cuidadosa, cuando no dé lugar a ambigüedades y siempre en código de usuario final, nunca en librerías de funciones de uso general, pues en tal caso destruye los principios de organización y modularidad que es uno de los principales objetivos de NameBlock.
En un programa concreto de usuario que va a hacer uso intensivo de un NameBlock de nombre muy largo se podría considear razonable crear un alias como por ejemplo:
NameBlock _aia = std::math::stat::model::mle::arima::aia;
4. Reglas de publicación o globalización
- 4.1.En determinadas circunstancias los miembros públicos o de sólo lectura de un NameBlock pueden pasar al ámbito global, es decir, se puede acceder a ellos sin explicitarlo con ::. A este hecho se le llamará publicar un NameBlock y se logra con el operador Real UsingNameBlock(NameBlock nameblock_idt) y es similar al using namespace de C++, aunque con ciertas diferencias.
- 4.2.El operador UsingNameBlock devuelve cierto si es compatible con el resto de variables de tipo NameBlock actualmente globalizadas, es decir, si no contiene ningún miembro público con el mismo nombre que otro. Los miembros que ya estuvieran presentes no serán accesibles de forma implícita pero sí mediante ::, y se mostrará un mensaje de aviso para advertirlo.
- 4.3.Como es lógico, este operador de globalización no surte efecto si se llama en ambiente local, lo cual incluye dentro de un NameBlock, y devolverá falso y un mensaje de error si se intenta.
- 4.4.El NameBlock permanecerá en uso mientras siga existiendo el objeto devuelto por este operador. Por ejemplo, si se ha incluido en un fichero estará presente hasta que se descompile dicho fichero.
- 4.5.Si existe una variable global previa con el mismo nombre ocultará el miembro NameBlock publicado que seguirá siendo accesible con ::, como es lógico. Si se crea la variable global posteriormente se actuará del mismo modo.
- 4.6.Se trata de una sentencia no declarativa sino ejecutiva por lo que no tiene efecto si se recupera de un módulo OIS.
- 4.7.Al igual que con los alias del apartado 3.3., se debe ser sumamente cauteloso con este tipo de sentencias y no incluirlas en librerías de caracter general. Sería prudente usarlas sólo en programas de usuario final y cuando no haya ninguna posible ambigüedad de nomenclatura. Puede ser particularmente útil para mantener compatibilidad hacia atrás de código creado previamente a la existencia de NameBlock.
5. Reglas de ámbito
Un NameBlock tiene su propia tabla de símbolos que se usa cuando está activo, es decir, cuando se está ejecutando alguna de sus funciones miembro. Sólo puede haber activo un NameBlock al mismo tiempo o bien no haber ninguno activo en cuyo caso todo funciona como hasta ahora. El orden de búsquedas de símbolos de TOL que anteriormente se reducía a símbolos de ámbito local y ámbito global, pasa a ser el siguiente:
- 5.1. Miembro local: Si hay un NameBlock activo se busca ahí.
- 5.2. Ámbito local: Si no lo había o bien no contiene el nombre buscado y estamos en ambiente local se busca en la pila local.
- 5.3. Ámbito global: Si aún no se ha encontrado nada en los dos puntos anteriores se busca en la tabla global de símbolos.
- 5.4. Miembro publicado: Si sigue sin encontrarse el símbolo se buscará en la tabla de NameBlock publicados.
Cuando se llama a una función de un NameBlock se activa el mismo y la búsqueda de variables, funciones y estructuras locales le da total prioridad a los miembros de ese NameBlock de forma que se pueden usar todos ellos, tanto públicos como privados.
Esto reduce el uso actual de scope dinámico en la pila local de llamadas de funciones, lo cual puede ayudar a medio plazo a reconducir a los usuarios de TOL a los estándares de programación.
Ventajas
Las ventajas que aportan los NameBlock's se pueden resumir en que da facilidades para alcanzar mayor modularidad, mejor organización y legibilidad y mayor reusabilidad del código.
Modularidad
La principal utilidad del NameBlock es dotar a TOL de un mecanismo de modularidad más allá del fichero que permita construir módulos, paquetes y librerías robustamente organizados. Una forma prudente de anidar los NameBlock's sería en este caso: lib_id::package_id::module_id
Legibilidad
Pero un NameBlock podría también ayudar a la legibilidad del código en tareas más humildes, al sustituir llamadas a set[num_field] por set::name_field sin el coste computacional añadido de setname_field? y sin tener que definir estructuras de datos espúreas para usar set->name_field
Eliminación de variables globales
Un caso especialmente recomendable es el que afecta a todos los parámetros de configuración y variables globales en general que deberían incluirse dentro de un único NameBlock global para evitar colisiones de nombres.
//Parámetros de configuración del proyecto NameBlock cfg = [[ //Configuración del acceso a la base de datos NameBlock db = [[Text alias, Text user, Text pwd, Text host, ...]], //Configuración de los directorios más utilizados NameBlock path = [[Text source, Text data, Text log, ...]], ... ]]; Real DBOpen(cfg::db::alias, cfg::db::user, cfg::db::pwd);
Flexibilidad
Las funciones que devuelven conjuntos heterogéneos también ganarían flexibilidad, expresibilidad y facilidad de mantenimiento y uso sin recurrir a la definición de estructuras locales
NameBlock my_stats(Serie ser) {[[ Real num = CountS (ser); Real avg = AvrS(ser); Real stdv = StDsS(ser) ]]}; NameBlock stats = my_stats(SubSer(Gaussian(0,1,C),y2000,y2000m12d31)); Real stats::stdv;
- Aunque se añadan elementos a lo que devuelve la función o se reordenen los mismos seguirá funcionando el código que usara esta función accediendo con
- mientras no se cambien los nombres:
NameBlock my_stats(Serie ser) {[[ Real num = CountS (ser); Real avg = AvrS(ser); Real mdn = MedianS(ser); Real stdv = StDsS(ser) ]]};
Generación automática de documentación
A efectos de generación automática de documentación de un NameBlock, se cuenta con la función Set Members(NameBlock root) que devuelve un listado de los miembros públicos contenidos en un NameBlock y sus hijos de forma recursiva, con un registro para cada uno con la siguiente estructura informativa ordenada de la forma natural indicada por los propios campos:
Struct NameBlockInfoStruct { Text nameBlock_, //full name block path a::b::... Text mode_, //"Variable", "Function" or "Struct" Text grammar_, //"Real", "Text", "Set", "NameBlock", ... Text memberName_, //Internal public member name Text filePath_, //Relative path to the root file path Text description_ //User description };
Miembros de documentación accesibles de sólo lectura
Son miembros de sólo lectura de la forma _.autodoc.<sufix>
- _.autodoc.description: Si un NameBlock contiene un miembro privado Text _.autodoc.description, entonces su contenido pasa a ser la descripción del NameBlock sin necesidad de usar la función PutDescription.
- _.autodoc.keys: Si un NameBlock contiene un miembro privado Set _.autodoc.keys, entonces sus elementos, que han de ser todos de tipo Text, pasan a ser palabras claves utilizadas en los procedimientos de búsqueda temática.
- _.autodoc.url: Si un NameBlock contiene un miembro privado Text _.autodoc.url este se usará en los mecanismos de generación automática para enlazar a la URL especificada en la que se explicará de forma más detallada el funcionamiento del NameBlock.
- _.autodoc.authors: Si un NameBlock contiene un miembro privado Set _.autodoc.authors este se usará en los mecanismos de generación automática para identificar a los autores del código.
Funciones de información relacionadas
- Text GetNameBlock(Anything obj): Devuelve el nombre completo del NameBlock al que pertenece un objeto. Si no pertenece a ninguno devuelve la cadena vacía "".
- Text FullName(Anything obj): Devuelve el nombre completo de un objeto, incluyendo el prefijo de su NameBlock si está dentro de uno.