Answer
t.me/js_testРешений два
const seq1 = ( x, $ = console.log( ( seq1(x - 1, x > 1 ? void x : x), x ) ) ) => null; const seq2 = ( x, $ = x > 1 ? seq2(x - 1) : void x, _ = console.log(x) ) => null;
Принцип работы
Идея решения использует особенности того, как вычисляются функции, для которых определены аргументы по-умолчанию. Если у функции они есть, то при функция при вызове будет вычислена в два прохода:
- в первый проход будут вычислены значения инициализаторов для аргументов, у которых переданное значение отсутствует или равно
undefined
; - во второй проход будет вычислено само тело функции;
Во втором проходе имена из первого прохода станут доступны как родительское пространство имен.
Это означает, что функция такого вида:
function foo(arg1, arg2 = do_something(), arg3 = do_whatever(arg2)) { return [ arg1, arg2, arg3 ]; }
является синтаксическим сахаром для следующей равноценной функции:
function foo(#arg1, #arg2, #arg3) { let arg1 = #arg1; let arg2 = #arg2 !== undefined ? #arg2 : do_something(); let arg3 = #arg3 !== undefined ? #arg3 : do_whatever(arg2); return (() => { return [ arg1, arg2, arg3 ]; })(); }
Изнутри кода, вычисляющего инициализаторы, сама функция уже доступна по имени, поэтому может быть вызвана рекурсивно.
Подробнее про это поведение можно почитать на MDN и в спецификации ECMAScript.
Объяснение
Решение можно построить как минимум двумя путями — явно выйдя из рекурсии по собственному условию; или неявно выйдя из рекурсии, основываясь на том, что инициализатор не вычисляется для аргумента со значением, отличным от undefined
.
Первое решение рассахаривается в следующую конструкцию:
const seq1 = (_x, _$) => { let x = _x; let $ = _$ !== undefined ? _$ : console.log( seq1(x - 1, x > 1 ? void x : x), x ) return (() => null)(); }
Что, в свою очередь, упрощается до:
const seq1 = (x, stopNow) => { if (!stopNow) { if (x > 1) { seq1(x - 1, true); } else { seq1(x - 1); } console.log(x); } return null; }
Т.е. мы входим в рекурсию всегда, но если вторым аргументом передан флаг "хватит уже", то мы сразу же из нее выходим.
Второе решение рассахаривается в следующий код:
const seq2 = (_x, _$, __) => { let x = _x; let $ = _$ !== undefined ? _$ : (x > 1 ? seq2(x - 1) : void x); let _ = __ !== undefined ? __ : console.log(x); return (() => null)(); }
Что, в свою очередь, упрощается до:
const seq2 = (x) => { if (x > 1) { seq2(x - 1); } console.log(x); return null; }
В отличие от первого решения, мы даже не пробуем входить в рекурсию, если условие не выполняется.
Код для проверки
const seq1 = ( x, $ = console.log( ( seq1(x - 1, x > 1 ? void x : x), x ) ) ) => null; const seq2 = ( x, $ = x > 1 ? seq2(x - 1) : void x, _ = console.log(x) ) => null; seq1(5); // 1 2 3 4 5 console.log("---"); seq2(5); // 1 2 3 4 5