Answer

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


Report Page