close Warning: Can't synchronize with repository "(default)" (/var/svn/tolp does not appear to be a Subversion repository.). Look in the Trac log for more information.

Opened 15 years ago

Closed 14 years ago

#792 closed doubt (fixed)

Options and optional arguments

Reported by: pgea@… Owned by: Víctor de Buen Remiro
Priority: high Milestone:
Component: Various Version:
Severity: major Keywords: options, optional arguments
Cc:

Description

En el desarrollo de los proyectos, pero aún más ahora con el uso de clases y la creación de métodos surge la duda de cuál es la manera más recomendable de tratar las opciones y los argumentos opcionales.

En el documento adjunto repaso algunas situaciones en las que esto ocurre y algunas maneras de afrontarlo.

Creo que sería conveniente encontrar la manera más recomendada de hacer esto en TOL y apoyarlo con un conjunto de funciones que facilite su uso y quizá algunos ejemplos.

Attachments (1)

Opciones y argumentos opcionales.doc (52.5 KB) - added by pgea@… 15 years ago.

Download all attachments as: .zip

Change History (3)

Changed 15 years ago by pgea@…

comment:1 Changed 15 years ago by Víctor de Buen Remiro

Sólo me he leído el documento un poco por encima pero creo que que falta una forma de pasar opciones que es lo que hace por ejemplo BSR con la clase @Config. La idea es crear un clase de configuración ad-hoc que tiene valores por defecto para todos los argumentos opcionales con lo cual el usuario sólo tiene que especificar los que quiere modificar.

Podría proponerse crear una clase llamada como la propia función seguida de un sufijo del tipo.cfg, o sea

//Clase de configuración de opciones para la función mi.funcion
Class @mi.funcion.cfg
{
  Real opt_1 = 1;
  ...
  Text opt_n = "";
  //Constructor por defecto
  Static @mi.funcion.cfg Default(Real void) { @mi.funcion.cfg default }
};

Real mi.funcion(<argumentos_obligatorios>, @mi.funcion.cfg cfg) 
{ 
  //La función no tiene que preocuparse de nada porque el reconocedor sintáctico
  //se ocupa de todo y además lo hace muy rápido. Simplemente usa los miembros 
  //de la instancia de configuración que le han pasado
  Real cfg::opt_1;
  ...
  Text cfg::opt_n;
  ... 
}

//Se pueden dejar todas las opciones por defecto
Real x1 = mi.funcion(<argumentos_obligatorios>, @mi.funcion.cfg::Default());

//Se pueden especificar sólo algunas
Real x2 = mi.funcion(<argumentos_obligatorios>, {@mi.funcion.cfg cfg=[[
  Real opt_1 = 2 ]]});

//Si se especifica más de una el orden de aparición es arbitrario
Real x3 = mi.funcion(<argumentos_obligatorios>, {@mi.funcion.cfg cfg=[[
  Real opt_n = "lkjdf",
  Real opt_1 = 2 ]]});

Para situaciones complejas, en las que hay varias funciones y clases que requieren un conjunto de opciones comunes habría que buscar un nombre adecuado para la clase de configuración que se refiera al concepto que se quiere configurar. Lo más usual es que todas las funciones que lo usen seencuentren dentro de un mismo NameBlock en el que no haya más que una clase de configuración que en tal caso se puede llamar simplemente @Config. Para el caso de librerías o paquetes de código que aspiren a ser medianamente generales creo que es la única salida honrosa y compatibe con el paradigma OOP.

Pero claro, también es una forma más pesada de programar, porque tienes que crear una clase diferente para cada entorno de configuración, hay que buscarle un nombre, etc. Cuando se trata de un código muy local que no aspira a ser parte de una librería, o bien cuando tienes demasiadas funciones con su propio esquema de configuración pues puede ser engorroso.

Todo lo que sea meter las opciones en conjuntos, NameBlock o Class genérica, supone la pérdida de la capacidad del parser para reconocer posibles errores pues en un Set o un NameBlock cabe prácticamente cualquier cosa, y luego ese trabajo lo debe hacer la función TOL, bien a su manera bien con getOptArg. Y eso, además de ser peligroso y dificil de leer, puede ser también demasiado lento, por ser un lenguaje interpretado. Sin embargo, cuando la velocidad no es problema porque se ejecuta pocas veces y se trata de un código local puede ser más rápido de desarrollar que crear una clase de configuración ad-hoc, buscarle un nombre, etc. En estos casos sí que podría ser recomendable utilizar una clase de configuración genérica @Options como la que sugieres, y que no me parece nada rebuscado sino una evolución natural del getOptArg, aunque yo creo que debería ser algo más robusto, que compruebe también que no se pasan opciones desconocidas que podrían desorientar al usuario que se equivoque por una letra en el nombre, por ejemplo.

Sobre las instancias especiales en el caso de la clase de configuración genérica basta con definir un método estático de constructor

  Static @Options Default(Real void) {
    @Options options = [[ Set _options = Empty ]] }

Yo creo que cada método puede tener sus ventajas y sus incovenientes y no estoy seguro aún de si hay uno que sea el mejor en todos los casos. Quizás se podría dar un serie de recomendaciones para el uso de la forma más adecuada a cada caso. En cierta manera la clase de configuración ad-hoc supone más trabajo para el programador de la función y la genérica más trabajo para el usuario de la misma. Yo en principio soy partidario de la configuración ad-hoc.

Con respecto a las funciones con número variable de argumentos es algo bastante complicado de extender a las funciones de usuario por limitaciones del parser y especialmente del evaluador.

Por cuestiones similares resulta difícil tener funciones sin argumentos, incluso en C++ donde deben tomar el aspecto de variables globales como Time. Quizás se podría estudiar crear un tipo de argumento especial que sea estrictamente inutilizable por sintaxis. Incluso se podría intentar ver si utilizando algún truco de filtrado previo pudiera darse la apariencia de que acepta la secuencia fun() rellenándola internamente con algo así como fun(Real #void#) para que el propio sistema hiciera el trabajo sucio. Además, como el reconocedor de TOL no va a aceptar el identificador #void# no hay peligro de que se use sin querer. pero como digo esto es sólo una idea que aún no sé si es factible.

Por último habría que dedir que el uso de Anything es algo completamente distinto a los argumentos opcionales. Aquí podemos hablar de una forma de simular la sobrecarga de funciones propiamente dicha, pues lo que están haciendo es permitir introducir la misma información bajo distintos formatos, que pueden resultar más sencillos de utilizar según el contenido de la misma. Es decir se trata de algo complementario al problema de la configuración de opciones. Aquí habría que aplicar un poco el sentido común y ver en cada caso si es mejor usar Anything o aplicar algún prefijo o sufijo al nombre de la función que diferencie las situaciones. Yo creo que sólo merece la pena usar Anything si hay mucha casuística, es decir, si hay combinación de varios argumentos que pueden dar lugar a demasiadas funciones casi homónimas.

comment:2 Changed 14 years ago by Víctor de Buen Remiro

Resolution: fixed
Status: newclosed
Note: See TracTickets for help on using tickets.