Este artículo explora la arquitectura interna de los lenguajes de programación, específicamente aquellos con características inspiradas en Haskell, a los que se refiere como "Lil' Fun Langs". El objetivo es desmitificar cómo funcionan estos lenguajes, más allá de su sintaxis superficial. La explicación se centra en las decisiones de diseño que influyen en su comportamiento y rendimiento.
¿Cómo funcionan? El proceso de compilación (o interpretación) se divide en varias fases. Primero, el código fuente se transforma en un flujo de tokens (lexing), luego en un árbol de sintaxis abstracta (AST) superficial (parsing). Después, este AST se refina (desugaring) y se realiza la inferencia de tipos para generar un AST tipado. Este AST tipado se convierte en árboles de casos (pattern match compilation) y luego en una Representación Intermedia (IR) normalizada. Esta normalización puede tomar diferentes formas, como la Forma Normal de Lambda (ANF) o la forma STG (Spineless Tagless G-machine), dependiendo del enfoque de evaluación (lazy o strict). La IR normalizada se transforma en código de bajo nivel (closure conversion, code generation), que finalmente se convierte en código ejecutable (asm/bytecode/C/LLVM). En compilaciones nativas, se realiza la asignación de registros (register allocation). Finalmente, un sistema de tiempo de ejecución (runtime system) gestiona aspectos como la recolección de basura (GC) y la ejecución de funciones primitivas.
Evaluación Lazy vs. Strict: Una diferencia clave reside en la estrategia de evaluación. La evaluación strict (como en ML o OCaml) evalúa los argumentos de una función antes de pasarlos. La evaluación lazy (como en Haskell) solo evalúa los argumentos cuando su valor es necesario, lo que permite la definición de colecciones infinitas y optimiza el rendimiento al evitar cálculos innecesarios. Sin embargo, la evaluación lazy introduce complejidad en el sistema de tiempo de ejecución.
Otras consideraciones: El artículo también aborda otros aspectos como la diferencia entre lenguajes curried (donde una función de múltiples argumentos se descompone en una cadena de aplicaciones de funciones) y bland (que usan tuplas o múltiples parámetros), la distinción entre compiladores bootstrapped (que se compilan a sí mismos) y hosted (que usan un lenguaje existente para su compilación), y la diferencia entre lenguajes interpretados y compilados. Finalmente, se discuten los sistemas de manejo de errores, destacando la importancia de proporcionar mensajes de error informativos y precisos.
En resumen, el artículo ofrece una visión profunda de la arquitectura de los lenguajes de programación, revelando las decisiones de diseño que influyen en su comportamiento, rendimiento y complejidad.
