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는 인수로 전달되어 필요한 경우에만 다음 반복에서 호출됩니다 .
fix :: (a -> a) -> a
있으며, a
can은 원하는만큼 많은 인수의 함수일 수 있습니다. 이것은 정적 타이핑이 실제로 성가신 것을 의미하지는 않습니다.
나는 이것을 몇 년 전에 쓴 설명 인 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)
쓰셨습니까?
처음부터 이것을 구축하려고 시도 할 때 사용되는지 궁금합니다. 보자 기본적인 재귀 요인 함수는 다음과 같습니다.
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);
};
});
recurser
. 그것이 무엇을하고 있는지, 왜 그런지 모릅니다.
recurser
은 우리의 재귀 버전을 제공하기 때문에 기능은이 목표를 향한 첫 번째 단계는 fact
그 이름 자체를 참조 적이 있습니다.
function Y(recurse) { return recurse(recurse); } let factorial = Y(creator => value => { return value == 0 ? 1 : value * creator(creator)(value - 1); });
. 그리고 이것이 내가 그것을 소화하는 방법입니다 (올 바르면 확실하지 않습니다) : 함수를 명시 적으로 참조하지 않으면 ( 콤비 네이터로 허용되지 않음 ) 부분적으로 적용 / 커리 닝 된 함수 (작성자 함수 및 계산 함수)를 사용할 수 있습니다 계산 함수의 이름없이 재귀를 달성하는 람다 / 익명 함수를 만들 수있는 것은 무엇입니까?
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()
정의에 사용 하기 때문에). 와이.
arguments.callee
엄격 모드에서는 사용할 수 없습니다. developer.mozilla.org/en/JavaScript/…
(function fact(n){ return n <= 1? 1 : n * fact(n-1); })(5)
기능적 프로그래밍에 대해 깊이 경험하지 않고 지금 시작하지 않아도 약간 호기심이 많은 프로그래머에게 :
Y 결합기는 함수가 이름을 가질 수 없지만 인수로 전달되고 반환 값으로 사용되며 다른 함수 내에 정의 될 수있는 상황에서 재귀를 구현할 수있는 수식입니다.
함수 자체를 인수로 전달하여 작동하므로 스스로 호출 할 수 있습니다.
그것은 실제로 수학이지만 효과적으로 프로그래밍 언어이며 컴퓨터 과학, 특히 기능적 프로그래밍의 기초가되는 람다 미적분학의 일부입니다.
프로그래밍 언어가 함수의 이름을 지정하는 경향이 있기 때문에 Y 결합기의 일상적인 실제 가치는 제한되어 있습니다.
경찰 라인업에서 식별해야 할 경우 다음과 같습니다.
Y = λf. (λx.f (xx)) (λx.f (xx))
당신은 일반적으로 반복 때문에 그것을 발견 할 수 있습니다 (λx.f (x x))
.
λ
기호는 그 이름 수학 람다를 제공 그리스 문자 람다이다, 그리고 많은 거기에 (λx.t)
그 어떤 람다 계산법의 모습처럼 때문에 스타일 용어.
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))
.
고정 소수점 조합기는 fix
정의에 의해 동등성을 만족시키는 고차 함수 입니다.
forall f. fix f = f (fix f)
fix f
x
고정 소수점 방정식에 대한 해 를 나타냅니다
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
추상화 ( "함수") 본문에서 변수의 모든 자유 발생을 대체합니다 . 에타 감소는 중복 추상화를 제거합니다. 때로는 형식주의에서 생략되기도합니다. 기약 더 감소 규칙이 적용되지되는 식은,에 정상 또는 정규형 .B
E
λ 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
람다 미적분학에서 결합기 는 자유 변수가없는 추상화입니다. 가장 간단하게 : I
ID 조합 자
λ 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])
λ x . x
, 오늘 어떠세요?
다음은 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);
Y- 콤비 네이터는 플럭스 커패시터의 다른 이름입니다.
저는 Clojure와 Scheme의 Y-Combinator에 일종의 "바보 가이드"를 작성하여 스스로를 이해하는 데 도움을주었습니다. "The Little Schemer"의 자료에 영향을받습니다.
계획에서 : https://gist.github.com/z5h/238891
또는 Clojure : https://gist.github.com/z5h/5102747
두 튜토리얼 모두 주석과 함께 코드가 삽입되어 있으며 좋아하는 편집기로 잘라서 붙여 넣을 수 있습니다.
콤비 네이터 초보자 인 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 - 1
f
결과 를 다음 과 같이 누적합니다.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 - 1
x - 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
경우 ) 실제 는 아직 정의되지 않았습니다.fn
fr
fr
fn
실제 비즈니스 로직을 정의한다. 기준 fn
, Y
생성 fr
- 특정 형태의 도우미 함수 -에 대한 계산을 용이하게하기 위해 fn
A의 순환 방법.현재이 Y
방법을 이해하는 데 도움이되었으므로 도움 이 되길 바랍니다.
BTW, Lambda Calculus를 통한 함수형 프로그래밍에 대한 소개 책 이 매우 훌륭하다는 것을 알았 Y
습니다.
다음은 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 언급 대부분의 절차 언어 정전기 입력을 가지고있다.
따라서 이것에 대답하십시오 – 실제로는 아닙니다.
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 조합기가 분기 / 무한 루프)가 필요합니다.
이것에 대답하는 가장 좋은 방법은 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 조합기가 해결하는 문제를 이해할 수 있습니다.