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


392

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

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

12
약간의 팁, 만약 당신이 나처럼 기능적 언어에 대해 배우고 있다면, 콤비
— 네이터

3
이 질문 :)의 편집자의 Gravatar에 미소를 얻었다 MADS Torgensen의 블로그에 관련 링크
— Benjol


1
나는 Y 컴비 네이터 에 대한 나의 이해를 공유하는 짧은 요점을 썼습니다 : gist.github.com/houtianze/b274e4b975a28fe08aee681699c3f7d0 "Y 컴비 네이터가 재귀 기능을 만드는 방법"을 설명했습니다.
— ibic

1
이 질문은 어떻게 "너무 광범위"합니까?
— Rey Miyasaka

답변:


201

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


14
그래도 링크 이상입니다. 매우 간단한 요약 링크입니다 . 더 긴 요약을 부탁드립니다.
— Martijn Pieters

2
그것은 링크 일뿐이지만 이것보다 나아질 수는 없습니다. 이 답변은 종료 할 기본 조건이없는 (add1 투표) 자격이 있습니다. 일명 무한 재귀.
— Yavar

7
@Andre MacFie : 노력에 대해서는 언급하지 않았고 품질에 대해서는 언급했습니다. 일반적으로 스택 오버플로에 대한 정책은 추가 정보에 대한 링크와 함께 답변이 자체 포함되어야한다는 것입니다.
— Jørgen Fogh

1
@galdre가 맞습니다. 훌륭한 링크이지만 링크 일뿐입니다. 또한 아래의 3 가지 다른 답변에서도 언급되었지만 그 자체만으로도 모든 설명이 잘 뒷받침되어 있습니다. 이 답변은 OP의 질문에 대답조차하지 않습니다.
— toraritte

290

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는 인수로 전달되어 필요한 경우에만 다음 반복에서 호출됩니다 .


5
왜 오, 왜 그것을 'Y'와 'F'파라미터로 불러야 했습니까! 그들은 형식 ​​인수에서 길을 잃습니다!
— Brian Henk

3
Haskell에서는 다음과 같이 추상화 재귀를 수행 할 수 fix :: (a -> a) -> a있으며, acan은 원하는만큼 많은 인수의 함수일 수 있습니다. 이것은 정적 타이핑이 실제로 성가신 것을 의미하지는 않습니다.
— Peaker

12
Mike Vanier의 설명에 따르면 Y에 대한 정의는 재귀 적이므로 실제로 결합기가 아닙니다 . "(거의) 명시 적 재귀 제거 (게으른 버전)"에서 C # 코드에 해당하는 게으른 체계를 갖지만 포인트 2에 설명되어 있습니다. "정의 본문의 Y는 자유 변수이기 때문에 결합기가 아닙니다. Y- 콤비 네이터의 멋진 점은 함수의 고정 소수점을 평가하여 재귀를 생성한다는 것입니다. 이런 식으로 명시적인 재귀가 필요하지 않습니다.
— GrantJ

@GrantJ 당신은 좋은 지적을합니다. 이 답변을 게시한지 몇 년이 지났습니다. Vanier의 게시물을 훑어 보면 이제 Y를 썼지 만 Y 콤비 네이터는 쓰지 않았습니다. 곧 그의 게시물을 읽고 수정 내용을 게시 할 수 있는지 확인하겠습니다. 내 직감은 C #의 엄격한 정적 타이핑으로 인해 결국 C #을 막을 수 있다고 경고하지만, 내가 할 수있는 것을 볼 수 있습니다.
— Chris Ammerman

1
@WayneBurkett 수학에서 꽤 일반적인 관행입니다.
— YoTengoUnLCD

102

나는 이것을 몇 년 전에 쓴 설명 인 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 (n) { return builder(builder)(n);}대신에 builder(builder)쓰셨습니까?
— v7d8dpo4

@ v7d8dpo4 카레를 사용하여 2 변수의 함수를 1 변수의 고차 함수로 바꾸고 있었기 때문입니다.
— btilly

이것이 폐쇄가 필요한 이유입니까?
— TheChetan

1
@TheChetan 클로저를 사용하면 익명 함수 호출 뒤에 사용자 정의 동작을 연결할 수 있습니다. 그것은 또 다른 추상화 기술입니다.
— btilly

85

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

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);
    };
});

JavaScript에서 비슷한 설명 : igstan.ro/posts/…
— Pops

1
당신이 기능을 소개했을 때 당신은 나를 잃었다 recurser. 그것이 무엇을하고 있는지, 왜 그런지 모릅니다.
— Mörre

2
명시 적으로 재귀 적이 지 않은 함수에 대한 일반적인 재귀 솔루션을 구축하려고합니다. recurser은 우리의 재귀 버전을 제공하기 때문에 기능은이 목표를 향한 첫 번째 단계는 fact그 이름 자체를 참조 적이 있습니다.
— Wayne

@WayneBurkett 수, 나는이 같은 Y 연결자를 다시 작성 : function Y(recurse) { return recurse(recurse); } let factorial = Y(creator => value => { return value == 0 ? 1 : value * creator(creator)(value - 1); });. 그리고 이것이 내가 그것을 소화하는 방법입니다 (올 바르면 확실하지 않습니다) : 함수를 명시 적으로 참조하지 않으면 ( 콤비 네이터로 허용되지 않음 ) 부분적으로 적용 / 커리 닝 된 함수 (작성자 함수 및 계산 함수)를 사용할 수 있습니다 계산 함수의 이름없이 재귀를 달성하는 람다 / 익명 함수를 만들 수있는 것은 무엇입니까?
— neevek

50

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

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

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


24

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가 마법을 어떻게 사용하는지에 대한 전체 설명을 보려면 링크 된 기사를 확인 하십시오 .


6
당신이 arguments.callee를 사용하여 현재 기능을 액세스 할 수 있기 때문에 자바 스크립트가 익명의 재귀를 할 수있는 Y-콤비를 필요로하지 않는다 (참조 en.wikipedia.org/wiki/... )
— xitrium

6
arguments.callee엄격 모드에서는 사용할 수 없습니다. developer.mozilla.org/en/JavaScript/…
— dave1010

2
어떤 함수에도 이름을 부여 할 수 있으며, 함수 표현식 인 경우 해당 이름은 함수 자체에만 알려집니다. (function fact(n){ return n <= 1? 1 : n * fact(n-1); })(5)
— Esailija

1
IE를 제외하고. kangax.github.io/nfe
— VoronoiPotato

18

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

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

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

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

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

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

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

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

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


이것이 정답이어야합니다. BTW,와 U x = x x, Y = U . (. U)(표기 하스켈-처럼 남용). 적절한 조합기를 갖춘 IOW Y = BU(CBU). 따라서 Yf = U (f . U) = (f . U) (f . U) = f (U (f . U)) = f ((f . U) (f . U)).
— Will Ness

13

익명 재귀

고정 소수점 조합기는 fix정의에 의해 동등성을 만족시키는 고차 함수 입니다.

forall f.  fix f  =  f (fix f)

fix fx고정 소수점 방정식에 대한 해 를 나타냅니다

               x  =  f x

자연수의 계승은 다음과 같이 증명할 수 있습니다.

fact 0 = 1
fact n = n * fact (n - 1)

을 사용하면 fix일반 / μ- 재귀 함수에 대한 임의의 구성 증명이 비영리 자기 참조없이 파생 될 수 있습니다.

fact n = (fix fact') n

어디

fact' rec n = if n == 0
                then 1
                else n * rec (n - 1)

그런

   fact 3
=  (fix fact') 3
=  fact' (fix fact') 3
=  if 3 == 0 then 1 else 3 * (fix fact') (3 - 1)
=  3 * (fix fact') 2
=  3 * fact' (fix fact') 2
=  3 * if 2 == 0 then 1 else 2 * (fix fact') (2 - 1)
=  3 * 2 * (fix fact') 1
=  3 * 2 * fact' (fix fact') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (fix fact') (1 - 1)
=  3 * 2 * 1 * (fix fact') 0
=  3 * 2 * 1 * fact' (fix fact') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (fix fact') (0 - 1)
=  3 * 2 * 1 * 1
=  6

이 공식적인 증거는

fact 3  =  6

재 작성을 위해 고정 소수점 조합기를 동등하게 사용

fix fact'  ->  fact' (fix fact')

람다 미적분학

이 지정되지 않은 람다 미적분 형식주의는 문맥 자유 문법에있다

E ::= v        Variable
   |  λ v. E   Abstraction
   |  E E      Application

여기서 v함께와 변수 범위에 걸쳐 베타 및 ETA 환원 규칙

(λ x. B) E  ->  B[x := E]                                 Beta
  λ x. E x  ->  E          if x doesn’t occur free in E   Eta

베타 축소 는 표현 ( "인수")으로 x추상화 ( "함수") 본문에서 변수의 모든 자유 발생을 대체합니다 . 에타 감소는 중복 추상화를 제거합니다. 때로는 형식주의에서 생략되기도합니다. 기약 더 감소 규칙이 적용되지되는 식은,에 정상 또는 정규형 .BE

λ x y. E

속기

λ x. λ y. E

(추상 다원성),

E F G

속기

(E F) G

(응용 프로그램 왼쪽 연관성),

λ x. x

과

λ y. y

아르 알파 등가 .

추상화와 응용은 람다 미적분학의 유일한 "언어 프리미티브"이지만, 임의로 복잡한 데이터와 연산을 인코딩 할 수 있습니다.

교회 숫자는 Peano-axiomatic naturals와 유사한 자연수를 인코딩 한 것입니다.

   0  =  λ f x. x                 No application
   1  =  λ f x. f x               One application
   2  =  λ f x. f (f x)           Twofold
   3  =  λ f x. f (f (f x))       Threefold
    . . .

SUCC  =  λ n f x. f (n f x)       Successor
 ADD  =  λ n m f x. n f (m f x)   Addition
MULT  =  λ n m f x. n (m f) x     Multiplication
    . . .

공식적인 증거

1 + 2  =  3

베타 축소의 다시 쓰기 규칙 사용 :

   ADD                      1            2
=  (λ n m f x. n f (m f x)) (λ g y. g y) (λ h z. h (h z))
=  (λ m f x. (λ g y. g y) f (m f x)) (λ h z. h (h z))
=  (λ m f x. (λ y. f y) (m f x)) (λ h z. h (h z))
=  (λ m f x. f (m f x)) (λ h z. h (h z))
=  λ f x. f ((λ h z. h (h z)) f x)
=  λ f x. f ((λ z. f (f z)) x)
=  λ f x. f (f (f x))                                       Normal form
=  3

조합기

람다 미적분학에서 결합기 는 자유 변수가없는 추상화입니다. 가장 간단하게 : IID 조합 자

λ x. x

항등 함수에 동형

id x = x

이러한 결합기 는 SKI 시스템과 같은 결합기 계산 의 기본 연산자입니다 .

S  =  λ x y z. x z (y z)
K  =  λ x y. x
I  =  λ x. x

베타 감소는 강력하게 정상화 되지 않습니다 . 모든 환원성 표현 인 "환원"이 베타 감소 하에서 정상적인 형태로 수렴되는 것은 아닙니다. 간단한 예는 오메가 ω콤비 네이터의 다양한 적용입니다

λ x. x x

그 자체로 :

   (λ x. x x) (λ y. y y)
=  (λ y. y y) (λ y. y y)
. . .
=  _|_                     Bottom

가장 왼쪽 하위 표현 ( "헤드")의 감소가 우선합니다. 적용 순서는 대체 전에 인수를 정규화하지만 정상 순서 는 그렇지 않습니다. 두 가지 전략은 열성적인 평가 (예 : C)와 게으른 평가 (예 : Haskell)와 유사합니다.

   K          (I a)        (ω ω)
=  (λ k l. k) ((λ i. i) a) ((λ x. x x) (λ y. y y))

열렬한 적용 순서 베타 감소로 분기

=  (λ k l. k) a ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ y. y y) (λ y. y y))
. . .
=  _|_

엄격한 의미론 이후

forall f.  f _|_  =  _|_

그러나 게으른 정상 순서 베타 감소로 수렴

=  (λ l. ((λ i. i) a)) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  a

표현식의 형식이 정상인 경우 정규 차수 베타 감소로이를 찾습니다.

와이

고정 소수점 조합기 의 필수 특성Y

λ f. (λ x. f (x x)) (λ x. f (x x))

~에 의해 주어진다

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

동등성

Y g  =  g (Y g)

동형이다

fix f  =  f (fix f)

형식화되지 않은 람다 미적분은 일반 / μ 재귀 함수에 대한 임의의 구성 적 증거를 인코딩 할 수 있습니다.

 FACT  =  λ n. Y FACT' n
FACT'  =  λ rec n. if n == 0 then 1 else n * rec (n - 1)

   FACT 3
=  (λ n. Y FACT' n) 3
=  Y FACT' 3
=  FACT' (Y FACT') 3
=  if 3 == 0 then 1 else 3 * (Y FACT') (3 - 1)
=  3 * (Y FACT') (3 - 1)
=  3 * FACT' (Y FACT') 2
=  3 * if 2 == 0 then 1 else 2 * (Y FACT') (2 - 1)
=  3 * 2 * (Y FACT') 1
=  3 * 2 * FACT' (Y FACT') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (Y FACT') (1 - 1)
=  3 * 2 * 1 * (Y FACT') 0
=  3 * 2 * 1 * FACT' (Y FACT') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (Y FACT') (0 - 1)
=  3 * 2 * 1 * 1
=  6

(곱셈 지연, 합류)

Churchian 형식화되지 않은 람다 미적분학의 경우, 재귀 적으로 열거 가능한 고정 소수점 조합기의 무한대가 존재하는 것으로 나타났습니다 Y.

 X  =  λ f. (λ x. x x) (λ x. f (x x))
Y'  =  (λ x y. x y x) (λ y x. y (x y x))
 Z  =  λ f. (λ x. f (λ v. x x v)) (λ x. f (λ v. x x v))
 Θ  =  (λ x y. y (x x y)) (λ x y. y (x x y))
  . . .

정규 차수 베타 감소는 확장되지 않은 유형화되지 않은 람다 미적분을 Turing-complete rewrite 시스템으로 만듭니다.

Haskell에서는 고정 소수점 조합기를 우아하게 구현할 수 있습니다.

fix :: forall t. (t -> t) -> t
fix f = f (fix f)

하스켈의 게으름은 모든 하위 표현이 평가되기 전에 무한대로 정규화됩니다.

primes :: Integral t => [t]
primes = sieve [2 ..]
   where
      sieve = fix (\ rec (p : ns) ->
                     p : rec [n | n <- ns
                                , n `rem` p /= 0])


4
대답의 철저함에 감사하지만 첫 줄 바꿈 후 공식적인 수학 배경이 거의없는 프로그래머에게는 결코 접근 할 수 없습니다.
— Jared Smith

4
@ jared-smith이 대답은 Y 조합기의 CS / 수학 개념에 대한 보충적인 Wonkaian 이야기를 제공하기위한 것입니다. 아마도 친숙한 개념에 대한 가능한 가장 좋은 유추는 이미 다른 답변자에 의해 그려 졌다고 생각합니다. 개인적으로, 나는 항상 좋은 비유를 통해 아이디어 의 근본적인 진기함 인 진정한 기원에 직면하는 것을 좋아했습니다 . 나는 가장 광범위한 유추를 부적절하고 혼란스럽게 생각합니다.

1
안녕하세요, 신원 조합기 λ x . x, 오늘 어떠세요?
— MaiaVictor

이 답변이 가장 마음에 듭니다 . 방금 모든 질문을 해결했습니다!
— 학생

11

다른 답변은 하나의 중요한 사실없이 이것에 대해 매우 간결한 답변을 제공합니다.이 복잡한 방식으로 실제 언어로 고정 소수점 조합기를 구현할 필요가 없으며 실제적인 목적을 제공하지 않습니다 ( "예외, 나는 Y 조합을 알고 있습니다. ")입니다. 중요한 이론적 개념이지만 실질적인 가치는 거의 없습니다.


6

다음은 Y-Combinator 및 Factorial 함수의 JavaScript 구현입니다 (Douglas Crockford의 기사 : http://javascript.crockford.com/little.html 참조 ).

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

var factorial = Y(function (fac) {
    return function (n) {
        return n <= 2 ? n : n * fac(n - 1);
    };
});

var number120 = factorial(5);

6

Y- 콤비 네이터는 플럭스 커패시터의 다른 이름입니다.


4
매우 재밌습니다. :) 젊은 사람들은 참고 문헌을 인식하지 못할 수도 있습니다.
— Will Ness

2
ㅋ! 그래, 젊은 사람은 (나)는 여전히 ... 이해할 수

나는 그것이 진짜라고 생각했고 나는 여기서 끝났다. youtube.com/watch?v=HyWqxkaQpPw 최근 기사 futurism.com/scientists-made-real-life-flux-capacitor
— Thinkar Nay Htoo

이 답변은 영어가 아닌 사람들에게 특히 혼란 스러울 수 있다고 생각합니다. 이 주장을 이해하기 위해 꽤 많은 시간을 할애 할 수도 있습니다. (이것이 마음에 든다면이 질문에 답하고 배우는 사람이 낙담 한 것을 알게되면 기분이 나빠질 것입니다)
— mike

5

저는 Clojure와 Scheme의 Y-Combinator에 일종의 "바보 가이드"를 작성하여 스스로를 이해하는 데 도움을주었습니다. "The Little Schemer"의 자료에 영향을받습니다.

계획에서 : https://gist.github.com/z5h/238891

또는 Clojure : https://gist.github.com/z5h/5102747

두 튜토리얼 모두 주석과 함께 코드가 삽입되어 있으며 좋아하는 편집기로 잘라서 붙여 넣을 수 있습니다.


5

콤비 네이터 초보자 인 Mike Vanier의 기사를 찾았습니다. (Nicholas Mancuso 덕분에)가 정말 도움이된다는 . 이해를 문서화하는 것 외에도 다른 사람들에게 도움이 될 수 있다면 요약을 작성하고 싶습니다.

에서 엉터리 로 적은 엉터리

계승을 예로 사용하여 다음 almost-factorial함수를 사용하여 숫자의 계승을 계산합니다.x .

def almost-factorial f x = if iszero x
                           then 1
                           else * x (f (- x 1))

위의 의사 코드 almost-factorial에서 기능 f과 숫자를 받습니다.x ( almost-factorial커리되므로 함수를 수행 f하고 1-arity 함수를 반환하는 것으로 볼 수 있습니다 ).

의 almost-factorial계승을 계산할 때 x의 계승 계산을 위임합니다.x - 1f 결과 를 다음 과 같이 누적합니다.x (- X 1)이 경우의 결과 (X 곱한다).

그것은 팩토리얼 함수 almost-factorial의 crappy 버전 (tilt 만 계산할 수 있음 x - 1) 을 취하고 덜 클래시 버전의 factorial (tilt을 계산 )을 반환하는 것으로 볼 수 있습니다 x. 이 양식에서와 같이 :

almost-factorial crappy-f = less-crappy-f

크롤링 이 적은 버전의 factorial을 반복해서 전달하면 almost-factorial원하는 factorial 함수를 얻게됩니다 f. 다음과 같이 간주 될 수있는 곳 :

almost-factorial f = f

수정 점

그 almost-factorial f = f의미 f는 기능 의 수정 점 입니다almost-factorial .

이것은 위의 함수의 관계를 보는 정말 흥미로운 방법이었습니다. (그렇지 않은 경우 수정 지점에 대한 Mike의 게시물을 읽으십시오)

세 가지 기능

일반화하기 위해, 우리는이 비 재귀 기능 fn(우리의 거의 계승 등) 우리는이 수정 포인트 기능을 fr(우리 F 등), 그 다음 무엇을 Y수행하면 줄 때입니다 Y fn, Y의 고정 소수점 기능을 반환합니다 fn.

따라서 요약하면 ( fr단 하나의 매개 변수 만 사용 한다고 가정하면 단순화됩니다 . 재귀에서 , ...로 x퇴화합니다 ).x - 1x - 2

  • 우리는 핵심 계산 을 fn다음 def fn fr x = ...accumulate x with result from (fr (- x 1))과 같이 정의합니다 . 이것은 거의 유용한 기능입니다. fn직접 사용할 수는 없지만 x곧 유용 할 것입니다. 이 비 재귀 fn는 함수 fr를 사용하여 결과를 계산합니다.
  • fn fr = fr, fr의 수정 점이다 fn, fr는 IS 유용한 funciton, 우리가 사용할 수 있습니다 fr에 x우리의 결과를 얻을 수
  • Y fn = fr, Y함수의 수정 점을 반환 Y 하고 거의 유용한 함수 fn를 유용하게 만듭니다. fr

파생 Y(포함되지 않음)

나는 파생을 건너 뛰고 Y이해로 간다 Y. Mike Vainer의 게시물에는 많은 세부 정보가 있습니다.

의 형태 Y

Y( 람다 미적분학 형식으로) 다음 과 같이 정의됩니다 .

Y f = λs.(f (s s)) λs.(f (s s))

s함수의 왼쪽에 있는 변수 를 바꾸면

Y f = λs.(f (s s)) λs.(f (s s))
=> f (λs.(f (s s)) λs.(f (s s)))
=> f (Y f)

실제로 결과 (Y f)는f .

왜 (Y f) 작동합니까?

의 서명을 따라 f, (Y f)단순화하기 위해, 모든 인수에 대응하는 기능을 할 수있다, 이제 가정하자 (Y f)우리의 계승 기능처럼, 하나 개의 매개 변수를 사용합니다.

def fn fr x = accumulate x (fr (- x 1))

이후 fn fr = fr, 우리는 계속

=> accumulate x (fn fr (- x 1))
=> accumulate x (accumulate (- x 1) (fr (- x 2)))
=> accumulate x (accumulate (- x 1) (accumulate (- x 2) ... (fn fr 1)))

가장 안쪽 (fn fr 1)이 기본 사례이고 fn사용하지 않으면 재귀 계산이 종료됩니다.fr 계산에 .

Y다시 보고 :

fr = Y fn = λs.(fn (s s)) λs.(fn (s s))
=> fn (λs.(fn (s s)) λs.(fn (s s)))

그래서

fr x = Y fn x = fn (λs.(fn (s s)) λs.(fn (s s))) x

나 에게이 설정의 마법 부분은 다음과 같습니다.

  • fn그리고 fr서로 상호 의존하십시오 : fr'포장' fn내부, 매번 fr계산에 사용되며 x, '스폰'( '리프트'?) fn및 계산을 위임합니다 fn(자체 전달 fr및 x). 반면 에 작은 문제의 결과를 계산 하는 데 fn의존 fr하고 사용 fr합니다 x-1.
  • 당시는 fr정의에 사용 되었지만 ( 작업에서 사용 하는 fn경우 ) 실제 는 아직 정의되지 않았습니다.fnfrfr
  • 그것은의 fn실제 비즈니스 로직을 정의한다. 기준 fn, Y생성 fr- 특정 형태의 도우미 함수 -에 대한 계산을 용이하게하기 위해 fnA의 순환 방법.

현재이 Y방법을 이해하는 데 도움이되었으므로 도움 이 되길 바랍니다.

BTW, Lambda Calculus를 통한 함수형 프로그래밍에 대한 소개 책 이 매우 훌륭하다는 것을 알았 Y습니다.


5

다음은 Nicholas Mancuso 의 답변에 언급 된 기사 (TOTALY 가치가 있는 기사) 에서 컴파일 한 원래 질문 에 대한 답변 과 다른 답변입니다.

Y- 콤비 네이터 란 무엇입니까?

Y- 콤비 네이터는 재귀 적이 지 않은 함수 인 단일 인수를 취하는 "함수"(또는 고차 함수-다른 함수에서 작동하는 함수)로, 함수의 버전을 반환합니다. 재귀.


다소 재귀 적 =)이지만 더 깊이있는 정의 :

콤비 네이터 — 자유 변수가없는 람다 식입니다.
자유 변수 — 바운드 변수가 아닌 변수입니다.
바운드 변수 — 변수 이름을 인수 중 하나로 갖는 람다 식 본문에 포함 된 변수입니다.

이것을 생각하는 또 다른 방법은 콤비 네이터가 람다 식이며, 콤비 네이터의 이름을 찾은 곳마다 그 정의로 바꿀 수 있으며 모든 것이 여전히 작동합니다 (콤비 네이터가 람다 몸체 내부에 자체 참조 포함).

Y- 콤비 네이터는 고정 소수점 조합기입니다.

함수의 고정 점은 함수에 의해 자체적으로 매핑되는 함수 도메인의 요소입니다.
즉, 이것이 의미하는 경우 c함수의 고정 점입니다.f(x)f(c) = c
f(f(...f(c)...)) = fn(c) = c

결합기는 어떻게 작동합니까?

아래 예제는 강력한 + 동적 타이핑을 가정합니다 .

게으른 (정상 순서) Y- 조합기 :
이 정의는 게으른 (또한 지연되고 필요에 따라) 평가가있는 언어에 적용됩니다. 평가 전략은 값이 필요할 때까지 식의 평가를 지연시킵니다.

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

이것이 의미하는 것은 주어진 함수 f(비 재귀 함수)에 대해 먼저 계산하고 해당 λx.f(x x)람다 식을 자체에 적용 하여 해당 재귀 함수를 얻을 수 있다는 것입니다.

엄격한 (적용 순서) Y- 조합기 :
이 정의는 엄격한 (열심하고 욕심 많은) 평가-변수에 바인딩되자 마자식이 평가되는 평가 전략이있는 언어에 적용됩니다.

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

그것은 본질적으로 게으른 것과 동일하며 λ람다의 신체 평가를 지연시키기 위해 여분의 래퍼가 있습니다. 내가 물었다 한 또 다른 질문 약간이 주제와 관련된를.

그들은 무엇을 위해 좋은가?

Chris Ammerman이 대답 에서 빌린 도난 : Y- 콤비 네이터는 재귀를 일반화하고 구현을 추상화하여 해당 함수의 실제 작업과 분리합니다.

비록 Y- 콤비 네이터는 실제적인 응용을 가지고 있지만, 주로 이론적 인 개념으로, 전체적인 비전을 넓히고 분석 능력과 개발자 기술을 향상시킬 것입니다.

그것들은 절차 적 언어로 유용합니까?

으로 마이크 그리고 Vanier에 의해 진술 : 는 Y 콤비 자체가 아무튼 때문에 ', 많은 정적으로 입력 된 언어의 Y 연결자를 정의하는 것이 가능하지만 (내가 본 예에서 적어도) 이러한 정의는 일반적으로 일부 비 명백한 유형의 해커를 필요로 t는 정적 유형이 간단합니다. 그것은이 기사의 범위를 넘어서므로 더 이상 언급하지 않을 것이다.

로 그리고 크리스 Ammerman 언급 대부분의 절차 언어 정전기 입력을 가지고있다.

따라서 이것에 대답하십시오 – 실제로는 아닙니다.


4

y 결합기는 익명의 재귀를 구현합니다. 그래서 대신

function fib( n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

넌 할 수있어

function ( fib, n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

물론 y- 콤비 네이터는 이름 별 통화 언어에서만 작동합니다. 일반적인 값별 호출 언어로 이것을 사용하려면 관련 z 조합기 (y 조합기가 분기 / 무한 루프)가 필요합니다.


Y 콤비 네이터는 값별 통과 및 지연 평가와 함께 작동 할 수 있습니다.
— Quelklef 2016 년

3

고정 소수점 조합기 (또는 고정 소수점 연산자)는 다른 함수의 고정 소수점을 계산하는 고차 함수입니다. 이 작업은 언어 런타임 엔진의 명시적인 지원없이 재 작성 규칙의 형태로 재귀를 구현할 수 있기 때문에 프로그래밍 언어 이론과 관련이 있습니다. (src 위키 백과)


3

이 운영자는 당신의 삶을 단순화시킬 수 있습니다 :

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

그런 다음 추가 기능을 피하십시오.

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

마지막으로을 호출 fac(5)합니다.


0

이것에 대답하는 가장 좋은 방법은 JavaScript와 같은 언어를 선택하는 것입니다.

function factorial(num)
{
    // If the number is less than 0, reject it.
    if (num < 0) {
        return -1;
    }
    // If the number is 0, its factorial is 1.
    else if (num == 0) {
        return 1;
    }
    // Otherwise, call this recursive procedure again.
    else {
        return (num * factorial(num - 1));
    }
}

이제 함수 내부에서 함수 이름을 사용하지 않고 재귀 적으로 호출하도록 다시 작성하십시오.

함수 이름 factorial은 콜 사이트 에서만 볼 수 있습니다.

힌트 : 함수 이름은 사용할 수 없지만 매개 변수 이름은 사용할 수 있습니다.

문제를 해결하십시오. 찾지 마십시오. 일단 해결하면 y 조합기가 해결하는 문제를 이해할 수 있습니다.


1
해결하는 것보다 더 많은 문제가 발생하지 않습니까?
— 녹 티스 스카이 타워

1
녹 티스, 질문을 명확히 할 수 있습니까? y- 콤비 네이터 자체의 개념이 해결하는 것보다 더 많은 문제를 일으키는 지 묻고 있습니까, 아니면 특히 JavaScript를 사용하여 시연하기로 선택한 것에 대해 구체적으로 이야기하고 있습니까? 내가 설명 했어?
— zumalifeguard
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.