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 factorial
Y가 사용되는 줄을 살펴보십시오 . recurse
내부 함수에서도 나중에 사용되는 매개 변수 (이 예에서는 )가 있는 함수를 전달 합니다. 매개 변수 이름은 기본적으로 내부 함수의 이름이되어 재귀 호출을 수행 할 수 있습니다 ( recurse()
정의에 사용 하기 때문에). 와이.
답변
기능적 프로그래밍에 대해 깊이 경험하지 않고 지금 시작하지 않아도 약간 호기심이 많은 프로그래머에게 :
Y 결합기는 함수가 이름을 가질 수 없지만 인수로 전달되고 반환 값으로 사용되며 다른 함수 내에 정의 될 수있는 상황에서 재귀를 구현할 수있는 수식입니다.
함수 자체를 인수로 전달하여 작동하므로 스스로 호출 할 수 있습니다.
그것은 실제로 수학이지만 효과적으로 프로그래밍 언어이며 컴퓨터 과학, 특히 기능적 프로그래밍의 기초가되는 람다 미적분학의 일부입니다.
프로그래밍 언어가 함수의 이름을 지정하는 경향이 있기 때문에 Y 결합기의 일상적인 실제 가치는 제한되어 있습니다.
경찰 라인업에서 식별해야 할 경우 다음과 같습니다.
Y = λf. (λx.f (xx)) (λx.f (xx))
당신은 일반적으로 반복 때문에 그것을 발견 할 수 있습니다 (λx.f (x x))
.
λ
기호는 그 이름 수학 람다를 제공 그리스 문자 람다이다, 그리고 많은 거기에 (λx.t)
그 어떤 람다 계산법의 모습처럼 때문에 스타일 용어.
