lunes, 15 de febrero de 2016

Lisp vs. C++ (fe de erratas)

Hace unos días publiqué un post comentando que había empezado a leer el SICP y que había tomado como ejercicio adicional armar mi propio intérprete de LISP para probar los ejemplos. Lo hice sin estudiar antes cual es la forma correcta de hacerlo, tratando de ir descubriéndola y corrigiéndola sobre la marcha, y pensando más en un código compacto y prolijo, que en un código eficiente. Como el resultado efectivamente salió rápido y fue conciso, en aquel post comenté a grandes rasgos las ideas principales y compartí el código. Pero resulta que tenía, como era de esperarse, errores importantes.

Principalmente, estaba mal el manejo de scopes, y se notaba en particular al crear lambdas ya que las capturas no funcionaban debidamente. No encontré una solución fácil al problema, entendiendo por "fácil" el hecho de que se resuelva cambiando unas pocas líneas en alguna función. Tuve que cambiar la estructura de datos subyacente que usaba para representar los ambientes, pasando de un simple mapa a una clase que representa una jerarquía de mapas enlazados, y debido a la complejidad de su ciclo de vida pasando de la "allocación" estática de los mismos al uso de std::shared_ptr.

El problema fue muy fácil de encontrar (solo avanzar con los ejemplos del libro), pero muy difícil de corregir, ya que el código reflejaba el hecho de que yo mismo no tenía claro cómo se suponía que debía funcionar. Tuve que echar mano a un intérprete real (usé mit-scheme) para probar, comparar, explorar, y jugar un poco hasta terminar de descifrar la verdadera lógica detrás de todo esto. Y eso no hizo más que corroborar lo que había adelantado: no hay mejor forma de aprender cómo funciona algo que explicándoselo a alguien que no tiene idea al respecto. Ese alguien (o algo), para muchas cuestiones algorítmicas, puede ser como en este caso el "sr. compilador".

 Ahora las lambdas capturan los valores que deben capturar y funcionan correctamente!

En resumen, la segunda versión de este intérprete incrementa la longitud de su fuente en un 30%, pero a cambio, agrega nuevos operadores predefinidos, algo más de documentación, unos pocos detalles de eficiencia, y, sobre todo, logra resolver correctamente todos los ejemplos del primer capítulo del libro (me tomé el trabajo de armar un script que los verifique, así que puedo asegurarlo). Ahora voy por el segundo capítulo, donde se empieza a jugar con estructuras de datos, partiendo de "cons", "car" y "cdr", y hasta el momento eso también viene funcionando como debe.

Finalmente, para seguir jugando con C++, hice otra cosa nunca hago: usar excepciones para el manejo de errores. He usado antes bibliotecas que tiran excepciones, y conozco en detalle cómo funcionan, pero por diferentes razones nunca las elijo para mis propias bibliotecas. Sin embargo, en este caso, parece ser efectivamente la forma más simple y recomendable.

La evaluación detecta errores (mayormente de lógica, no cubro todos los de
sintaxis) y los retorna al bucle principal mediante el mecanismo de excepciones.

Así que este post no fue más que una excusa para compartir la nueva versión del intérprete. Aunque no lo considero mucho más que un juguete, no quería quedarme con la versión anterior después de descubrir esos errores. Ahora sí cumple lo que promete, aunque no sé hasta cuando. Retomo el libro de tanto en tanto, así que seguramente encontraré otro contra-ejemplo más adelante y tendré que volver a retocarlo.

4 comentarios:

  1. >> me tomé el trabajo de armar un script que los verifique

    por favor puedes compartir ese script

    PD: no logro compilar el archivo, muestra
    using namespace std::literals;
    error: 'literals' is not a namespace-name

    ResponderEliminar
    Respuestas
    1. El error es por la versión de C++. Hay que usar C++14... con gcc es pasarle -std=c++14 (o -std=c++1y según la versión de gcc).
      Ahora no tengo el script a mano con los ejemplos, más tarde los subo.

      Eliminar
  2. Realizo los siguientes pasos, copio el código en un archivo en blanco en ZINJAI, luego trato de compilar, ¿en que parte le paso el parámetro std=c++14?

    ResponderEliminar
    Respuestas
    1. En las opciones de compilación (menú ejecución->opciones, argumentos extra), o usando la plantilla de c++14.

      Eliminar