Bun, JavaScript y TCO

La optimización de llamadas en cola en JavaScript —en particular los “proper tail calls” exigidos por la especificación ECMAScript— está recibiendo nueva atención porque Bun (a través de JavaScriptCore) la admite mientras que motores principales como V8 y SpiderMonkey, en gran medida, no lo hacen. Los comentaristas sopesan los beneficios prácticos para estilos recursivos o funcionales intensivos (por ejemplo, evitar desbordamientos de pila, CPS, máquinas de estados) frente a inconvenientes como una depuración más कठिन, semántica de pila alterada y bajo rendimiento cuando se combina con operaciones de arrays de JS y mutación. El intercambio también aborda cuestiones más amplias: si JavaScript debería inclinarse más hacia paradigmas funcionales, cuánto deberían seguir los motores la especificación cuando cambia el comportamiento observable, y el riesgo de que el código funcione en Bun pero falle o se ralentice en Node o Deno.

Significado de “TCO” y expectativas a partir del título

  • En este contexto, TCO = Tail Call Optimization (u “proper tail calls”), no Total Cost of Ownership ni otros acrónimos.
  • Varios lectores esperaban inicialmente un artículo sobre Total Cost of Ownership para Bun/JS y se sintieron algo decepcionados, pero aun así encontraron interesante el ángulo funcional.

Recursión, TCO y semántica de JavaScript

  • La optimización de llamadas en posición de cola se plantea como algo más que una “optimización”: cuando está garantizada, cambia la semántica del lenguaje al impedir el crecimiento de la pila para las llamadas en cola.
  • Los proper tail calls son obligatorios según la especificación de ECMAScript en modo estricto, pero la mayoría de los motores principales (V8, SpiderMonkey) ignoran esta parte. WebKit/JSC y Bun (que usa JSC) sí los implementan.
  • Algunos sostienen que la falta de TCO limita la expresividad (por ejemplo, CPS, funciones mutuamente recursivas, máquinas de estados codificadas como funciones que se llaman en cola).

Razones por las que los grandes motores JS evitan PTC

  • El problema principal citado: la experiencia de desarrollo. TCO elimina marcos de pila, lo que hace que los stack traces, los depuradores y las herramientas de informe de errores reflejen peor la estructura real de llamadas.
  • También se mencionan complicaciones de seguridad/límites de realm y complejidad de implementación.
  • Otros responden que los depuradores pueden mantener pilas sombra o metadatos; Scheme y Lua muestran que esto se puede resolver.
  • Algunos ven la resistencia como en parte “política” o anti-funcional, no puramente técnica.

Debates sobre rendimiento y estilo de código

  • Gran distinción entre:
    • Código recursivo de cola pero pesado en asignaciones (por ejemplo, construir nuevos arrays con spread en cada llamada), que es asintóticamente malo incluso con TCO.
    • Bucles imperativos o versiones basadas en mutación, que son mucho más rápidas y eficientes en memoria en los motores JS.
  • Muchos participantes encuentran el ejemplo recursivo/ternario difícil de leer comparado con un bucle simple; algunos lo llaman “ingenioso pero ilegible”.
  • Otros defienden el estilo funcional como perfectamente legible para quien está acostumbrado, pero admiten que los arrays y el GC de JS hacen ineficientes los patrones funcionales ingenuos.

Alcance y practicidad de TCO en JS

  • Varios argumentan que TCO es esencial en lenguajes puramente funcionales, pero menos crítica en un lenguaje multiparadigma y mutable donde los bucles son idiomáticos.
  • Preocupación: código que dependa de la TCO de Bun/JSC puede desbordar la pila en Node/Deno; se aconsejan comprobaciones en tiempo de ejecución o comentarios.
  • Se menciona como paralelo la falta deliberada de TCO en Python (por razones similares de depuración/legibilidad).

Notas específicas de Bun y del motor

  • Bun expone un intrínseco de JSC para llamadas en cola mediante una interfaz especial $vm, pero su uso indebido puede bloquear el proceso.
  • Se elogia Bun por su velocidad (por ejemplo, en instalaciones de dependencias); la paridad en tiempo de ejecución con el app router de Node/Next.js aún no está completa, pero está en la hoja de ruta.
  • Para AWS Lambda, Bun puede usarse mediante runtimes/contenedores personalizados, pero sin las optimizaciones de arranque en frío similares a las de Node.