Bun, JavaScript e TCO
A otimização de tail call em JavaScript – em particular as “proper tail calls” exigidas pela especificação ECMAScript – está recebendo atenção renovada porque Bun (via JavaScriptCore) a suporta, enquanto motores importantes como V8 e SpiderMonkey em grande parte não o fazem. Comentadores ponderam os benefícios práticos para estilos recursivos ou funcionais (por exemplo, evitar stack overflows, CPS, máquinas de estados) contra desvantagens como depuração mais difícil, semântica de pilha alterada e baixo desempenho quando combinada com operações de array e mutação em JS. A troca também aborda questões mais amplas: se o JavaScript deve se inclinar mais para paradigmas funcionais, quanto os motores devem seguir a especificação quando ela altera o comportamento observável, e o risco de código que funciona em Bun, mas falha ou fica lento em Node ou Deno.
Significado de “TCO” e expectativas em relação ao título
- Neste contexto, TCO = Tail Call Optimization (ou “proper tail calls”), e não Total Cost of Ownership ou outras siglas.
- Vários leitores inicialmente esperavam um artigo sobre Total Cost of Ownership para Bun/JS e ficaram um pouco desapontados, mas ainda acharam o ângulo funcional interessante.
Recursão, TCO e semântica do JavaScript
- A otimização de tail-call é apresentada como mais do que uma “otimização”: quando garantida, ela altera a semântica da linguagem ao impedir o crescimento da pilha em chamadas de cauda.
- As proper tail calls são exigidas pela especificação ECMAScript em strict mode, mas a maioria dos grandes motores (V8, SpiderMonkey) ignora essa parte. WebKit/JSC e Bun (que usa JSC) as implementam.
- Alguns argumentam que a falta de TCO limita a expressividade (por exemplo, CPS, funções mutuamente recursivas, máquinas de estados codificadas como funções que fazem tail-calls).
Razões pelas quais os principais motores de JS evitam PTC
- Principal problema citado: experiência do desenvolvedor. TCO elimina frames da pilha, tornando stack traces, depuradores e ferramentas de relatório de erros menos fiéis à estrutura real de chamadas.
- Há menções a complicações de segurança/fronteira de realms e à complexidade de implementação.
- Outros contrapõem que depuradores podem manter pilhas sombra ou metadados; Scheme e Lua mostram que isso é solucionável.
- Alguns veem a resistência como em parte “política” ou anti-funcional, não puramente técnica.
Debates sobre desempenho e estilo de código
- Grande distinção entre:
- Código recursivo em cauda, mas pesado em alocação (por exemplo, construir novos arrays com spread a cada chamada), que é assimptoticamente ruim mesmo com TCO.
- Versões imperativas com loops ou baseadas em mutação, que são muito mais rápidas e eficientes em memória nos motores de JS.
- Muitos participantes acham o exemplo recursivo/com operador ternário difícil de ler comparado a um simples loop; alguns o chamam de “esperto, mas ilegível”.
- Outros defendem o estilo funcional como perfeitamente legível para quem está acostumado, mas admitem que os arrays e o GC do JS tornam padrões funcionais ingênuos ineficientes.
Escopo e praticidade da TCO em JS
- Vários argumentam que TCO é essencial em linguagens puramente funcionais, mas menos crítica em uma linguagem multi-paradigma e mutável onde loops são idiomáticos.
- Preocupação: código que depende da TCO de Bun/JSC pode estourar a pilha em Node/Deno; checagens em tempo de execução ou comentários são recomendados.
- A falta deliberada de TCO em Python (por razões semelhantes de depuração/legibilidade) é citada como paralelo.
Notas específicas de Bun e do motor
- Bun expõe uma intrínseca de JSC para tail calls via uma interface especial
$vm, mas o uso indevido pode derrubar o processo. - Bun é elogiado pela velocidade (por exemplo, instalações de dependências); a paridade em runtime com o app router do Node/Next.js ainda não é completa, mas está no roadmap.
- Para AWS Lambda, Bun pode ser usado via runtimes/containers personalizados, mas sem as otimizações de cold start parecidas com as do Node.