JS, 꼬리 재귀 최적화(TCO)는 믿을 수 없다!
자바스크립트(JavaScript)의 꼬리 재귀 최적화(TCO)는 ES2015에 명시되었지만, 대부분의 엔진에서 제대로 구현되지 않음
재귀 함수(Recursive Function) 사용 시 스택 오버플로우(Stack Overflow) 발생 가능성을 인지하고, 반복문(Iteration) 또는 트램펄린(Trampoline) 패턴을 고려해야 함
TCO 미지원으로 인해 디버깅(Debugging) 시 스택 트레이스(Stack Trace)가 깨지는 문제 발생
Bun과 같은 새로운 런타임 환경에서 TCO 지원을 기대할 수 있지만, 호환성(Compatibility)을 위해 주의 필요
자바스크립트(JavaScript) 런타임 환경별 TCO 지원 현황
자바스크립트(JavaScript) 런타임 환경에 따라 꼬리 재귀 최적화(TCO) 지원 여부가 다르므로, 개발 시 주의가 필요하다. 크롬 V8(Chrome V8), Node.js V8 및 Deno V8에서는 TCO를 지원하지 않으며, 파이어폭스 스파이더몽키(Firefox SpiderMonkey) 또한 TCO를 안전하게 보장하지 않는다. 사파리 자바스크립트코어(Safari JavaScriptCore)는 버전별로 TCO 지원 여부가 달라 일관성을 기대하기 어렵다. Bun은 JavaScriptCore 기반이지만, 런타임 버전에 따라 동작이 달라지므로, 프로덕션 코드(Production Code)에서 TCO에 의존하는 것은 위험하다.
TCO 미지원으로 인한 디버깅(Debugging) 문제
꼬리 재귀 최적화(TCO)가 제대로 구현되지 않으면, 디버깅(Debugging) 과정에서 스택 트레이스(Stack Trace)가 예상과 다르게 나타나는 문제가 발생한다. Kwantuum의 댓글에 따르면, TCO는 스택 프레임을 재사용하기 때문에, 디버거(Debugger)가 이전 스택 프레임을 인식하지 못하여 코드 스텝(Code Step)이 제대로 이루어지지 않는다. 이러한 이유로 TCO 구현이 기피되었다는 분석도 있다. 따라서 TCO에 의존하는 코드는 디버깅 과정에서 어려움을 겪을 수 있다.
재귀 함수(Recursive Function)의 대안: 반복문(Iteration)과 트램펄린(Trampoline)
재귀 함수(Recursive Function) 대신 반복문(Iteration)을 사용하면 스택 오버플로우(Stack Overflow) 문제를 피할 수 있다. 반복문은 런타임 환경의 TCO 지원 여부에 관계없이 안정적으로 동작하며, 코드의 가독성(Readability)을 유지하면서 스택 사용량을 제어할 수 있다. 또한, 트램펄린(Trampoline) 패턴을 활용하여 재귀적인 구조를 유지하면서 스택 오버플로우를 방지할 수 있다. 트램펄린은 함수 호출을 반복적으로 수행하는 루프를 사용하여, 스택 프레임(Stack Frame) 생성을 최소화한다.
TCO를 위한 코드 구조의 중요성
TCO를 활용하기 위해서는 꼬리 재귀(Tail Recursion) 형태의 코드를 작성해야 한다. 꼬리 재귀는 함수 호출이 함수의 마지막 연산이어야 하며, 추가적인 연산이 없어야 한다. 하지만, kithalden의 댓글에 따르면, try/catch 블록 내에서 재귀 호출을 하거나, `return foo(x) + 0`과 같이 꼬리 호출 이후 연산을 추가하면 TCO가 적용되지 않는다. 따라서 TCO를 위해서는 코드 구조에 대한 이해가 필수적이며, 꼬리 재귀 형태를 유지하는 것이 중요하다.