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
그러나 이것은 내부 상태 (카운터 a
및 b
)를 노출하기 때문에 나쁩니다 . 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
콤비 ( range
와 reduce
)과의 유도체 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)
익명 함수만을 사용하여 재귀 적으로 계산됩니다.