jueves, 28 de abril de 2016

Registros en PSeInt: Paso 0

Uno de estos días quise empezar con la implementación de registros en PSeInt. Para esto hay que reestructurar algunas cosas, y quiero aprovechar la volteada para arreglar otras. Entre los principales cambios, está la forma representación interna de una variable (tanto de su tipo, como de su contenido). Un cambio así repercute por tooodas partes. Creyendo que ya tenía un panorama completo de lo que buscaba, mucha experiencia de los diseños anteriores, y una buena batería de tests automáticos, empecé a  escribir desde cero nuevas clases superpoderosas para tipos y datos contemplando las infinitas combinaciones (cosas como una referencia a un arreglo de registros con atributos escalares de tipos todavía sin definir). Después de completar el 90% de unos tres diseños diferentes y no poder cerrarlos y/o integrarlos, empecé a sospechar que así no iba a converger nunca.

En particular, el sistema de tipos actual (que ya reescribí una vez allá por 2009/2010) solo contemplaba originalmente los tres tipos escalares (numérico, lógico, y caracter) y las posibles ambigüedades entre ellos. Esto repercute directamente en las funciones que evalúan las expresiones, y sobre todo en la clase Memoria, que es la que guarda las variables. Sobre esta base, agregué en su momento un primer parche para manejar arreglos. Hasta ahí, más o menos bien, ese parche no era tan malo. Pero luego, con los subprocesos, llegó otro parche para representar referencias. Este ya fue bastante más terrible. No puedo sumar un tercer parche para registros.

Surge así la pregunta de siempre: ¿rediseñar de cero, o corregir de a poco? Como estaba pensando en principio solo en la clase Memoria, intenté rehacerla de cero. Pero no hubo caso. El resultado era incompleto, o imposible de integrar con el resto. El fracaso me hizo recordar lo horrible que son algunas partes de ese resto (incluso las que ya reescribí una vez). Así que tuve que empezar primero por corregir errores de diseño mucho más básicos.

Por ejemplo, había unas cuantas funciones de las principales en el sistema de evaluación de expresiones que recibían una variable de tipo tipo_var. A veces, allí iba el tipo de resultado que quería, y la evaluación lo usaba para ajustar los tipos de las variables. Otras veces era un placeholder para que la evaluación devuelva el tipo de dato del resultado. Esto se debe a que valor y tipo estaban separados (el valor se retornaba siempre como std::string, sin importar lo que realmente representara). Y para colmo de males, en algunos casos el mismo se usaba para ambas cosas a la vez. Entonces, corregir el uso de esas funciones era una pesadilla. Analizarlas, también, porque por dentro era casi tan crípticas como por fuera. La falta de documentación tampoco ayudaba. Así que tuve que empezar por arreglar esas funciones.


Nota importante: "Nunca volver a usar el pasaje por referencia como mecanismo de salida". Hay muy pocas excepciones válidas a esta regla, y cada vez mejores formas de evitarlo. Corolario: "No hay mejor documentación que el sistema de tipos". Yo ya lo había aprendido por las malas hace tiempo, ahora me parece casi obvio, pero acá estamos hablando de código bastante viejo.

En fin, acomodar estas funciones me hizo recordar que la mejor forma de diseñar la nueva Memoria es la que siempre les recomiendo a mis alumnos, y no la que estaba aplicando: desde afuera para adentro. Es decir, recomiendo empezar escribiendo (o reescribiendo en este caso) el código cliente, el que va a consumir los servicios de la nueva clase o función. Así nos enfocamos primero en cómo se usaría para resolver problemas concretos. Al "utilizarla" en el cliente estoy definiendo la interfaz que necesito o más me conviene, sin condicionarme por cuestiones internas de su implementación. Esa implementación será un problema más adelante. Porque de nada sirve empezar por implementaciones geniales si nos atan a interfaces pobres o inutilizables.


Como resultado de todo esto, ahora los "valores" en PSeInt son simil Variant. Un Variant puede tener dentro uno de varios tipos de datos, como un union, pero más type-safe. Entonces, en lugar de buscar una representación común a todos, utiliza la representación que mejor le conviene para cada uno. Evito así perder información y tiempo en las conversiones. Por ejemplo, si es numérico se guarda como double, si es lógico como bool, si es caracter como std::string. De esta forma, cada valor ahora es "consiente" del tipo de dato que lleva. Eso evita muchos errores, y simplifica las interfaces.

Ese cambio ya está funcionado en el repositorio. Ahora todos los valores que van y vienen en PSeInt son representados por estos Variants, y las funciones que evalúan las expresiones los aprovechan, y además ya no necesitan utilizar el pasaje por referencia como mecanismo de salida para el tipo. El siguiente paso será reestructurar la clase Memoria para que aproveche esa información en lugar de intentar separala nuevamente. Para eso antes tengo que quitar los parches para arreglos y referencias. Una vez que lo logre, para agregar nuevos tipos de datos solo tendré que extender ese Variant, pero para la clase Memoria y para las funciones que evalúan u operan con las expresiones ya no habrá grandes cambios.

Así será fácil, por ejemplo, eliminar la restricción de solo pasar arreglos a subprocesos por referencia. O también agregar la posibilidad de retornar arreglos, y copiarlos completos en una sola asignación. O de retornar más de un valor desde una función en pseudocódigo y entonces eliminar aquí también la necesidad de usar pasaje por referencia para eso. Y unas cuantas cosas más, que creo que simplificarán mucho el lenguaje e irán cerrando los agujeros que quedaron al agregar subprocesos. Por supuesto, todo configurable desde el perfil. Así que falta muchísimo trabajo, pero los registros ya están en camino.

6 comentarios:

  1. ¡Que bueno, registros en PSeInt!
    ¡¡Lo espero con alegría!!

    ResponderEliminar
  2. Hola, me gustaría saber si con este programa pseint tiene la capacidad de escribir y leer en archivos de texto.

    ResponderEliminar
    Respuestas
    1. No, por el momento no tiene nada para leer o escribir archivos.

      Eliminar
  3. Hola, quería saber si se avanzo sobre la idea de crear registros en PSeint... estoy buscando por la web y siempre caigo en los mismos sitios... nada concreto... baje la versión PSeint Rebel... pero no logro crearlos... alguna ayuda? Saludos!

    ResponderEliminar
  4. Hola, quería saber si se avanzo sobre la idea de crear registros en PSeint... estoy buscando por la web y siempre caigo en los mismos sitios... nada concreto... baje la versión PSeint Rebel... pero no logro crearlos... alguna ayuda? Saludos!

    ResponderEliminar
    Respuestas
    1. No mucho. Tengo que necesariamente que cerrar primero lo de reescribir el parser, y con eso va a ser más fácil agregar registros. Pero es un cambio muuuy grande el del parser, todavía falta bastante trabajo.

      Eliminar