[functional-programming] Y- 콤비 네이터 란 무엇입니까? [닫은]

Y- 콤비 네이터는 “기능적”측면에서 컴퓨터 과학 개념입니다. 대부분의 프로그래머는 콤비 네이터에 대해 들어 본 적이 없다면 전혀 알지 못합니다.

  • Y- 콤비 네이터 란 무엇입니까?
  • 결합기는 어떻게 작동합니까?
  • 그들은 무엇을 위해 좋은가?
  • 그것들은 절차 적 언어로 유용합니까?


답변

오랫동안 읽을 준비가 되었다면 Mike Vanier가 훌륭한 설명을 합니다. 간단히 말해 기본적으로 지원하지 않는 언어로 재귀를 구현할 수 있습니다.


답변

Y- 콤비 네이터는 “기능”(다른 기능에서 작동하는 기능)으로, 그 자체에서 함수를 참조 할 수 없을 때 재귀를 가능하게합니다. 컴퓨터 과학 이론에서는 재귀를 일반화 하고 구현을 추상화함으로써 문제의 기능의 실제 작업과 분리합니다. 재귀 함수에 컴파일 타임 이름이 필요하지 않은 이점은 일종의 보너스입니다. =)

람다 함수 를 지원하는 언어로 적용 할 수 있습니다 . 표현람다 기반 특성은 일반적으로 이름으로 자신을 참조 할 수 없음을 의미합니다. 그리고 변수를 선언하고 참조한 다음 람다를 할당하여 자체 참조 루프를 완성하는 방식 으로이 문제를 해결하는 것은 쉽지 않습니다. 람다 변수를 복사하고 원래 변수를 다시 할당하면 자체 참조가 중단됩니다.

Y- 콤비 네이터는 정적 유형 언어 ( 프로 시 저럴 언어가 자주 사용됨)에서 구현하고 사용하기에 번거 롭습니다. 일반적으로 입력 제한 사항은 문제가되는 함수가 컴파일시 알려지기 위해 인수의 수가 필요하기 때문입니다. 즉, y- 콤비 네이터를 사용해야하는 인수 개수에 대해 작성해야합니다.

아래는 C #에서 Y-Combinator의 사용법과 작동 방식의 예입니다.

Y- 콤비 네이터를 사용하는 것은 재귀 함수를 구성하는 “비정상적인”방법을 포함합니다. 먼저 함수 자체보다는 기존 함수를 호출하는 코드 조각으로 함수를 작성해야합니다.

// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);

그런 다음 함수를 호출하는 함수로 바꾸고 그렇게하는 함수를 반환합니다. 하나의 기능을 사용하고 다른 기능을 수행하는 작업을 수행하기 때문에 기능이라고합니다.

// A function that creates a factorial, but only if you pass in
// a function that does what the inner function is doing.
Func<Func<Double, Double>, Func<Double, Double>> fact =
  (recurs) =>
    (x) =>
      x == 0 ? 1 : x * recurs(x - 1);

이제 함수를 가져 와서 계승처럼 보이는 다른 함수를 반환하지만 자체 호출하는 대신 외부 함수에 전달 된 인수를 호출하는 함수가 있습니다. 이것을 계승으로 만드는 방법은 무엇입니까? 내부 함수를 자체로 전달하십시오. Y-Combinator는 영구 이름을 가진 함수이므로 재귀를 유발할 수 있습니다.

// One-argument Y-Combinator.
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> F)
{
  return
    t =>  // A function that...
      F(  // Calls the factorial creator, passing in...
        Y(F)  // The result of this same Y-combinator function call...
              // (Here is where the recursion is introduced.)
        )
      (t); // And passes the argument into the work function.
}

계승 호출 자체보다는 계승이 계승 생성기 (Y-Combinator에 대한 재귀 호출에 의해 리턴 됨)를 호출합니다. 그리고 t의 현재 값에 따라 생성기에서 반환 된 함수는 t-1로 생성기를 다시 호출하거나 1을 반환하여 재귀를 종료합니다.

복잡하고 비밀 스럽지만 모두 런타임에 흔들리며 작동의 핵심은 “지연된 실행”과 두 기능에 걸친 재귀의 분리입니다. 내부 F는 인수로 전달되어 필요한 경우에만 다음 반복에서 호출됩니다 .


답변

나는 이것을 몇 년 전에 쓴 설명 인 http://www.mail-archive.com/boston-pm@mail.pm.org/msg02716.html 에서 해제했습니다 .

이 예제에서는 JavaScript를 사용하지만 다른 많은 언어도 작동합니다.

우리의 목표는 변수 1 개만 있고 대입은없고, 이름으로 사물을 정의하는 등의 방법으로 1 개 변수의 재귀 함수를 작성하는 것입니다. (이것이 우리의 목표 인 이유는 다른 질문입니다. 불가능한 것 같아요? 예를 들어 계승을 구현해 봅시다.

1 단계는 우리가 조금만 부정하면 쉽게 할 수 있다고 말하는 것입니다. 2 개의 변수와 대입 함수를 사용하면 최소한 재귀를 설정하기 위해 대입을 사용하지 않아도됩니다.

// Here's the function that we want to recurse.
X = function (recurse, n) {
  if (0 == n)
    return 1;
  else
    return n * recurse(recurse, n - 1);
};

// This will get X to recurse.
Y = function (builder, n) {
  return builder(builder, n);
};

// Here it is in action.
Y(
  X,
  5
);

이제 우리가 덜 속일 수 있는지 봅시다. 우선 과제를 사용하고 있지만 꼭 그럴 필요는 없습니다. X와 Y를 인라인으로 작성할 수 있습니다.

// No assignment this time.
function (builder, n) {
  return builder(builder, n);
}(
  function (recurse, n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse, n - 1);
  },
  5
);

그러나 우리는 1 변수의 함수를 얻기 위해 2 변수의 함수를 사용하고 있습니다. 고칠 수 있을까요? Haskell Curry라는 이름의 똑똑한 사람은 깔끔한 트릭을 가지고 있습니다. 좋은 고차 함수가 있다면 1 변수의 함수 만 필요합니다. 증거는 다음과 같이 완전히 기계적인 텍스트 변환을 통해 2 (또는 일반적인 경우) 이상의 변수에서 1 개의 변수로 얻을 수 있다는 것입니다.

// Original
F = function (i, j) {
  ...
};
F(i,j);

// Transformed
F = function (i) { return function (j) {
  ...
}};
F(i)(j);

어디에서 … 그대로 유지됩니다. (이 속임수는 발명가의 이름을 따서 “카레 링”이라고합니다. 언어 Haskell은 Haskell Curry의 이름을 따서 명명되었습니다. 쓸모없는 퀴즈 아래있는 파일입니다.) 이제이 변환을 모든 곳에 적용하면 최종 버전을 얻게됩니다.

// The dreaded Y-combinator in action!
function (builder) { return function (n) {
  return builder(builder)(n);
}}(
  function (recurse) { return function (n) {
    if (0 == n)
      return 1;
    else
      return n * recurse(recurse)(n - 1);
  }})(
  5
);

자유롭게 사용해보십시오. alert ()을 반환하면 버튼에 연결하십시오. 이 코드는 할당, 선언 또는 두 변수의 함수를 사용하지 않고 계승을 재귀 적으로 계산합니다. (그러나 작동 방식을 추적하려고 시도하면 헤드 스핀이 발생할 수 있습니다. 파생하지 않고 약간만 다시 포맷하면 코드가 혼란스럽고 혼동 될 수 있습니다.)

재귀 적으로 계승을 정의하는 4 개의 행을 원하는 다른 재귀 함수로 바꿀 수 있습니다.


답변

처음부터 이것을 구축하려고 시도 할 때 사용되는지 궁금합니다. 보자 기본적인 재귀 요인 함수는 다음과 같습니다.

function factorial(n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}

fact계산 자체를 수행하는 대신 익명의 계승 계산 함수를 반환하는 새로운 함수를 리팩토링하고 작성해 보겠습니다 .

function fact() {
    return function(n) {
        return n == 0 ? 1 : n * fact()(n - 1);
    };
}

var factorial = fact();

조금 이상하지만 아무 문제가 없습니다. 우리는 각 단계에서 새로운 계승 함수를 생성하고 있습니다.

이 단계에서의 재귀는 여전히 상당히 명시 적입니다. fact함수는 자신의 이름을 인식 할 필요가있다. 재귀 호출을 매개 변수화하십시오.

function fact(recurse) {
    return function(n) {
        return n == 0 ? 1 : n * recurse(n - 1);
    };
}

function recurser(x) {
    return fact(recurser)(x);
}

var factorial = fact(recurser);

훌륭하지만 recurser여전히 자신의 이름을 알아야합니다. 그것도 매개 변수화하자 :

function recurser(f) {
    return fact(function(x) {
        return f(f)(x);
    });
}

var factorial = recurser(recurser);

이제 recurser(recurser)직접 호출하는 대신 결과를 반환하는 래퍼 함수를 ​​만들어 보겠습니다.

function Y() {
    return (function(f) {
        return f(f);
    })(recurser);
}

var factorial = Y();

이제 recurser이름을 완전히 제거 할 수 있습니다 . Y의 내부 함수에 대한 인수 일 뿐이며 함수 자체로 대체 될 수 있습니다.

function Y() {
    return (function(f) {
        return f(f);
    })(function(f) {
        return fact(function(x) {
            return f(f)(x);
        });
    });
}

var factorial = Y();

여전히 참조되는 유일한 외부 이름은 fact이지만, 이제는 쉽게 매개 변수화되어 완전한 일반 솔루션을 생성한다는 것이 분명해졌습니다.

function Y(le) {
    return (function(f) {
        return f(f);
    })(function(f) {
        return le(function(x) {
            return f(f)(x);
        });
    });
}

var factorial = Y(function(recurse) {
    return function(n) {
        return n == 0 ? 1 : n * recurse(n - 1);
    };
});


답변

위의 답변의 대부분은 Y-콤비가 무엇인지 설명 이다 하지만 무엇인가 에 대해 .

고정 소수점 조합기람다 미적분튜링 완료 되었음을 보여주기 위해 사용됩니다 . 이것은 계산 이론에서 매우 중요한 결과이며 함수형 프로그래밍에 대한 이론적 기초를 제공합니다 .

고정 소수점 조합기를 연구하면 함수형 프로그래밍을 실제로 이해하는 데 도움이되었습니다. 그래도 실제 프로그래밍에서 그 용도를 찾지 못했습니다.


답변

JavaScript의 y- 콤비 네이터 :

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

var factorial = Y(function(recurse) {
  return function(x) {
    return x == 0 ? 1 : x * recurse(x-1);
  };
});

factorial(5)  // -> 120

편집 : 코드를 살펴보면 많은 것을 배울 수 있지만 약간의 배경없이 삼키기가 약간 어렵습니다. 죄송합니다. 다른 답변으로 제시된 몇 가지 일반적인 지식을 통해 발생하는 문제를 구분할 수 있습니다.

Y 기능은 “y-combinator”입니다. 이제 var factorialY가 사용되는 줄을 살펴보십시오 . recurse내부 함수에서도 나중에 사용되는 매개 변수 (이 예에서는 )가 있는 함수를 전달 합니다. 매개 변수 이름은 기본적으로 내부 함수의 이름이되어 재귀 호출을 수행 할 수 있습니다 ( recurse()정의에 사용 하기 때문에). 와이.

Y가 마법을 어떻게 사용하는지에 대한 전체 설명을 보려면 링크 된 기사를 확인 하십시오 .


답변

기능적 프로그래밍에 대해 깊이 경험하지 않고 지금 시작하지 않아도 약간 호기심이 많은 프로그래머에게 :

Y 결합기는 함수가 이름을 가질 수 없지만 인수로 전달되고 반환 값으로 사용되며 다른 함수 내에 정의 될 수있는 상황에서 재귀를 구현할 수있는 수식입니다.

함수 자체를 인수로 전달하여 작동하므로 스스로 호출 할 수 있습니다.

그것은 실제로 수학이지만 효과적으로 프로그래밍 언어이며 컴퓨터 과학, 특히 기능적 프로그래밍의 기초가되는 람다 미적분학의 일부입니다.

프로그래밍 언어가 함수의 이름을 지정하는 경향이 있기 때문에 Y 결합기의 일상적인 실제 가치는 제한되어 있습니다.

경찰 라인업에서 식별해야 할 경우 다음과 같습니다.

Y = λf. (λx.f (xx)) (λx.f (xx))

당신은 일반적으로 반복 때문에 그것을 발견 할 수 있습니다 (λx.f (x x)).

λ기호는 그 이름 수학 람다를 제공 그리스 문자 람다이다, 그리고 많은 거기에 (λx.t)그 어떤 람다 계산법의 모습처럼 때문에 스타일 용어.