[javascript] 자바 스크립트 : 재귀 익명 함수?

기본 재귀 함수가 있다고 가정 해 보겠습니다.

function recur(data) {
    data = data+1;
    var nothing = function() {
        recur(data);
    }
    nothing();
}

다음과 같은 익명 기능이 있으면 어떻게 할 수 있습니까?

(function(data){
    data = data+1;
    var nothing = function() {
        //Something here that calls the function?
    }
    nothing();
})();

이 함수를 호출하는 함수를 호출하는 방법을 원합니다. 호출 된 함수의 이름을 알려줄 수있는 스크립트를 어딘가에서 보았습니다 (어디에서 기억 나지 않음). 그 정보를 지금 당장.



답변

함수를 “함수 선언”문이 아닌 값으로 생성하는 경우에도 함수에 이름을 지정할있습니다 . 다시 말해:

(function foo() { foo(); })();

스택 블로잉 재귀 함수입니다. 즉, Javascript의 다양한 구현에 이상한 문제가 있기 때문에 일반적으로 이것을 원하지 않을 수도 있습니다 . ( 참고 -꽤 오래된 댓글입니다. Kangax의 블로그 게시물에 설명 된 일부 / 다수 / 모든 문제는 최신 브라우저에서 수정 될 수 있습니다.)

이와 같은 이름을 지정하면 이름이 함수 외부에 표시되지 않습니다 (글쎄, 그렇게되어서는 안됩니다. 그것은 이상한 점 중 하나입니다). Lisp의 “letrec”과 같습니다.

에 관해서 arguments.callee는 약간의 최적화를 어렵게하기 때문에, 나쁜 일을 생각된다 일반적으로 “엄격”모드에서 허용하고있어 그. 또한 예상보다 훨씬 느립니다.

edit — 자신을 호출 할 수있는 “익명”함수의 효과를 얻으려면 다음과 같이 할 수 있습니다 (함수를 콜백 또는 이와 유사한 것으로 전달한다고 가정).

asyncThingWithCallback(params, (function() {
  function recursive() {
    if (timeToStop())
      return whatever();
    recursive(moreWork);
  }
  return recursive;
})());

그것이하는 일은 멋지고 안전하며 IE에서 깨지지 않는 함수 선언문 으로 함수를 정의하여 이름이 전역 네임 스페이스를 오염시키지 않는 로컬 함수를 만드는 것입니다. 래퍼 (진정한 익명) 함수는 해당 로컬 함수를 반환합니다.


답변

사람들은 코멘트에서 Y 결합 자에 대해 이야기했지만 아무도 답으로 작성하지 않았습니다.

Y 결합자는 다음과 같이 자바 스크립트로 정의 할 수 있습니다 : (링크에 대한 steamer25에게 감사드립니다)

var Y = function (gen) {
  return (function(f) {
    return f(f);
  }(function(f) {
    return gen(function() {
      return f(f).apply(null, arguments);
    });
  }));
}

익명 함수를 전달하려는 경우 :

(Y(function(recur) {
  return function(data) {
    data = data+1;
    var nothing = function() {
      recur(data);
    }
    nothing();
  }
})());

이 솔루션에 대해 주목해야 할 가장 중요한 점은 사용하지 않아야한다는 것입니다.


답변

U 결합 자

함수를 인자로 자신에게 전달함으로써 함수는 이름 대신 매개 변수를 사용하여 반복 될 수 있습니다! 따라서에 제공된 함수 U에는 함수 (자체)에 바인딩 할 매개 변수가 하나 이상 있어야합니다.

아래 예에서는 종료 조건이 없으므로 스택 오버플로가 발생할 때까지 무한 반복됩니다.

const U = f => f (f) // call function f with itself as an argument

U (f => (console.log ('stack overflow imminent!'), U (f)))

다양한 기술을 사용하여 무한 재귀를 멈출 수 있습니다. 여기에서는 입력을 기다리는 또 다른 익명 함수 를 반환하는 익명 함수를 작성 합니다. 이 경우 일부 숫자입니다. 숫자가 제공 될 때 0보다 크면 계속 반복되고 그렇지 않으면 0을 반환합니다.

const log = x => (console.log (x), x)

const U = f => f (f)

// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function

// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0

여기서 즉시 분명하지 않은 것은 우리의 함수가 처음 U결합 자를 사용하여 자신에게 적용될 때 첫 번째 입력을 기다리는 함수를 반환한다는 것입니다. 이것에 이름을 부여하면 람다 (익명 함수)를 사용하여 재귀 함수를 효과적으로 구성 할 수 있습니다.

const log = x => (console.log (x), x)

const U = f => f (f)

const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

이것은 직접 재귀 가 아닙니다 . 자체 이름을 사용하여 자신을 호출하는 함수입니다. 우리의 정의는 countDown본문 내부를 참조하지 않으며 여전히 재귀가 가능합니다.

// direct recursion references itself by name
const loop = (params) => {
  if (condition)
    return someValue
  else
    // loop references itself to recur...
    return loop (adjustedParams)
}

// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)

U 결합기를 사용하여 기존 함수에서 자체 참조를 제거하는 방법

여기에서는 자체 참조를 사용하는 재귀 함수를 취하고 자체 참조 대신 U 결합자를 사용하는 함수로 변경하는 방법을 보여줍니다.

const factorial = x =>
  x === 0 ? 1 : x * factorial (x - 1)
  
console.log (factorial (5)) // 120

이제 U 결합자를 사용하여 내부 참조를 factorial

const U = f => f (f)

const factorial = U (f => x =>
  x === 0 ? 1 : x * U (f) (x - 1))

console.log (factorial (5)) // 120

기본 교체 패턴은 이것입니다. 다음 섹션에서 비슷한 전략을 사용할 것입니다.

// self reference recursion
const foo =         x => ...   foo (nextX) ...

// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)

Y 결합 자

관련 : 거울 비유를 사용하여 설명 된 U 및 Y 결합 자

이전 섹션에서는 U 결합자를 사용하여 명명 된 함수에 의존하지 않는 재귀 함수로 자기 참조 재귀를 변환하는 방법을 보았습니다. 항상 함수를 첫 번째 인수로 자신에게 전달하는 것을 기억해야하는 약간의 성가심이 있습니다. 음, Y-combinator는 U-combinator를 기반으로하며 지루한 부분을 제거합니다. 복잡성 제거 / 감소가 우리가 함수를 만드는 주된 이유이기 때문에 이것은 좋은 것입니다.

먼저 우리 고유의 Y- 콤비 네이터를 유도 해 보겠습니다.

// standard definition
const Y = f => f (Y (f))

// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))

// remove reference to self using U combinator
const Y = U (h => f => f (x => U (h) (f) (x)))

이제 U-combinator와 비교하여 사용법을 살펴 보겠습니다. 되풀이하려면 U (f)단순히 호출하는 대신f ()

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

Y (f => (console.log ('stack overflow imminent!'),  f ()))

이제 다음을 countDown사용 하여 프로그램을 시연하겠습니다 Y. 프로그램은 거의 동일하지만 Y 조합기는 좀 더 깔끔하게 유지합니다.

const log = x => (console.log (x), x)

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)

countDown (5)
// 4 3 2 1 0

countDown (3)
// 2 1 0

그리고 이제 우리는 또한 볼 factorial것입니다

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const factorial = Y (f => x =>
  x === 0 ? 1 :  x * f (x - 1))

console.log (factorial (5)) // 120

보시다시피 f는 재귀 자체의 메커니즘이됩니다. 되풀이하려면 일반 함수처럼 호출합니다. 다른 인수로 여러 번 호출 할 수 있으며 결과는 여전히 정확합니다. 그것은 일반적인 기능 매개 변수이기 때문에, 우리는 우리가 같은 원하는대로 그 이름을 지정할 수 있습니다 recur아래 –

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (recur => n =>
  n < 2 ? n : recur (n - 1) +  (n - 2))

console.log (fibonacci (10)) // 55


둘 이상의 매개 변수가있는 U 및 Y 결합 자

위의 예에서 우리는 계산의 “상태”를 추적하기 위해 인수를 반복하고 전달할 수있는 방법을 보았습니다. 하지만 추가 상태를 추적해야하는 경우 어떻게해야합니까?

우리는 할 수 배열이나 뭐 같은 화합물 데이터를 사용하여 …

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => ([a, b, x]) =>
  x === 0 ? a : f ([b, a + b, x - 1]))

// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7])) 
// 0 1 1 2 3 5 8 13

그러나 이것은 내부 상태 (카운터 ab)를 노출하기 때문에 나쁩니다 . fibonacci (7)우리가 원하는 답을 얻기 위해 전화 를 걸 수 있다면 좋을 것 입니다.

커리 함수 (단항 (1 매개 변수) 함수의 시퀀스)에 대해 알고있는 것을 Y사용하면 복합 데이터 또는 고급 언어 기능에 대한 정의를 수정 하거나 의존 하지 않고도 목표를 쉽게 달성 할 수 있습니다.

fibonacci아래 의 정의를 자세히 살펴보십시오 . 우리는 즉시 적용 0하고 1있으며 각각 a과 바인딩됩니다 b. 이제 피보나치는에 바인딩 될 마지막 인수가 제공되기를 기다 x립니다. 재귀 할 때는 함수가 카레 형태이기 때문에 f (a) (b) (x)(아님 f (a,b,x))을 호출해야합니다 .

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const fibonacci = Y (f => a => b => x =>
  x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)

console.log (fibonacci (7)) 
// 0 1 1 2 3 5 8 13


이러한 종류의 패턴은 모든 종류의 함수를 정의하는 데 유용 할 수 있습니다. 우리가 사용하는 정의 된 두 가지 이상의 기능 볼 수 아래 Y콤비 ( rangereduce)과의 유도체 reduce, map.

const U = f => f (f)

const Y = U (h => f => f (x => U (h) (f) (x)))

const range = Y (f => acc => min => max =>
  min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])

const reduce = Y (f => g => y => ([x,...xs]) =>
  x === undefined ? y : f (g) (g (y) (x)) (xs))
  
const map = f =>
  reduce (ys => x => [...ys, f (x)]) ([])
  
const add = x => y => x + y

const sq = x => x * x

console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]

console.log (reduce (add) (0) ([1,2,3,4]))
// 10

console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]


모두 익명의 세상에

여기서는 순수 함수로 작업하기 때문에 이름이 지정된 함수를 정의로 대체 할 수 있습니다. 피보나치를 취하고 명명 된 함수를 표현으로 대체 할 때 어떤 일이 발생하는지보십시오

/* const U = f => f (f)
 *
 * const Y = U (h => f => f (x => U (h) (f) (x)))
 *
 * const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
 *
 */

/*
 * given fibonacci (7)
 *
 * replace fibonacci with its definition
 * Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 *
 * replace Y with its definition
 * U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
 * replace U with its definition
 * (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
 */

let result =
  (f => f (f)) (h => f => f (x => h (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
  
console.log (result) // 13

그리고 거기에 있습니다 – fibonacci (7)익명 함수만을 사용하여 재귀 적으로 계산됩니다.


답변

대신 “익명 객체”를 사용하는 것이 가장 간단 할 수 있습니다.

({
  do: function() {
    console.log("don't run this ...");
    this.do();
  }
}).do();

당신의 글로벌 공간은 완전히 오염되지 않았습니다. 매우 간단합니다. 또한 개체의 비전 역 상태를 쉽게 활용할 수 있습니다.

ES6 객체 메서드를 사용하여 구문을 더 간결하게 만들 수도 있습니다.

({
  do() {
    console.log("don't run this ...");
    this.do();
  }
}).do();


답변

나는 이것을 인라인 함수로하지 않을 것이다. 그것은 좋은 맛의 경계를 넘어서고 실제로 아무것도 얻지 못합니다.

정말로 필요하다면 arguments.calleeFabrizio의 대답과 같습니다. 그러나 이것은 일반적으로 바람직하지 않은 것으로 간주되며 ECMAScript Fifth Edition의 ‘엄격 모드’에서는 허용되지 않습니다. ECMA 3 및 non-strict-mode는 사라지지 않지만 엄격 모드에서 작업하면 더 많은 언어 최적화가 가능합니다.

명명 된 인라인 함수를 사용할 수도 있습니다.

(function foo(data){
    data++;
    var nothing = function() {
        foo(data);
    }
    nothing();
})();

그러나 IE의 JScript가 나쁜 일을하기 때문에 명명 된 인라인 함수 식도 피하는 것이 가장 좋습니다. 위의 예 foo에서 IE의 부모 범위 를 잘못 오염시키고 부모 foofoo내부 에서 본에 대한 별도의 인스턴스 foo입니다.

이것을 인라인 익명 함수에 넣는 목적은 무엇입니까? 부모 범위를 오염시키지 않으려면 물론 다른 자체 호출 익명 함수 (네임 스페이스) 안에 첫 번째 예제를 숨길 수 있습니다. nothing재귀와 관련하여 매번 새 복사본을 만들어야 합니까? 두 개의 단순한 상호 재귀 함수를 포함하는 네임 스페이스를 사용하는 것이 더 나을 수 있습니다.


답변

(function(data){
    var recursive = arguments.callee;
    data = data+1;
    var nothing = function() {
        recursive(data)
    }
    nothing();
})();


답변

다음과 같이 할 수 있습니다.

(foo = function() { foo(); })()

또는 귀하의 경우 :

(recur = function(data){
    data = data+1;
    var nothing = function() {
        if (data > 100) return; // put recursion limit
        recur(data);
    }
    nothing();
})(/* put data init value here */ 0);