Kombinator U
Przekazując sobie funkcję jako argument, funkcja może powtarzać się przy użyciu swojego parametru zamiast nazwy! Zatem funkcja podana do Upowinna mieć co najmniej jeden parametr, który będzie wiązał się z funkcją (samą).
W poniższym przykładzie nie mamy warunku wyjścia, więc będziemy po prostu pętli w nieskończoność, aż nastąpi przepełnienie stosu
const U = f => f (f) // call function f with itself as an argument
U (f => (console.log ('stack overflow imminent!'), U (f)))
Możemy zatrzymać nieskończoną rekurencję za pomocą różnych technik. Tutaj napiszę naszą anonimową funkcję zwracającą inną anonimową funkcję, która czeka na dane wejściowe; w tym przypadku pewna liczba. Po podaniu liczby, jeśli jest ona większa niż 0, będziemy kontynuować powtarzanie, w przeciwnym razie zwrócimy 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
Nie widać tutaj od razu, że nasza funkcja, gdy po raz pierwszy zastosowana do siebie za pomocą Ukombinatora, zwraca funkcję oczekującą na pierwsze wejście. Jeśli nadaliśmy temu nazwę, możemy skutecznie konstruować funkcje rekurencyjne przy użyciu lambd (funkcji anonimowych)
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
Tyle że to nie jest bezpośrednia rekursja - funkcja, która wywołuje samą siebie używając własnej nazwy. Nasza definicja countDownnie odwołuje się do siebie w swoim ciele i nadal możliwa jest rekurencja
// 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)
Jak usunąć samoodniesienie z istniejącej funkcji za pomocą kombinatora U.
Tutaj pokażę ci, jak wziąć funkcję rekurencyjną, która używa odwołania do samej siebie i zmienić ją na funkcję, która używa kombinatora U do zamiast odniesienia do siebie
const factorial = x =>
x === 0 ? 1 : x * factorial (x - 1)
console.log (factorial (5)) // 120
Teraz używamy kombinatora U, aby zastąpić odwołanie wewnętrzne do factorial
const U = f => f (f)
const factorial = U (f => x =>
x === 0 ? 1 : x * U (f) (x - 1))
console.log (factorial (5)) // 120
Podstawowy wzór wymiany jest następujący. Zwróć uwagę, że w następnej sekcji będziemy używać podobnej strategii
// self reference recursion
const foo = x => ... foo (nextX) ...
// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)
Kombinator Y.
powiązane: kombinatory U i Y wyjaśniono za pomocą analogii lustrzanej
W poprzedniej sekcji widzieliśmy, jak przekształcić rekursję z samoodniesieniami w funkcję rekurencyjną, która nie polega na nazwanej funkcji przy użyciu kombinatora U. Jest trochę irytujące, że trzeba pamiętać, aby zawsze przekazywać tę funkcję do siebie jako pierwszy argument. Cóż, kombinator Y opiera się na kombinatorze U i usuwa ten żmudny fragment. To dobrze, ponieważ usuwanie / zmniejszanie złożoności jest głównym powodem, dla którego tworzymy funkcje
Najpierw wyprowadźmy nasz własny kombinator 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)))
Teraz zobaczymy, jak jego użycie wypada w porównaniu z kombinatorem U. Zauważ, aby się powtórzyć, zamiast tego U (f)możemy po prostu zadzwonić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 ()))
Teraz zademonstruję countDownprogram przy użyciu Y- zobaczysz, że programy są prawie identyczne, ale kombinator Y sprawia, że wszystko jest trochę czystsze
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
A teraz zobaczymy factorialrównież
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
Jak widać, sam fstaje się mechanizmem rekursji. Aby się powtarzać, nazywamy to zwykłą funkcją. Możemy to wywołać wiele razy z różnymi argumentami, a wynik nadal będzie poprawny. A ponieważ jest to zwykły parametr funkcji, możemy nazwać go dowolnie, na przykład recurponiżej -
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
Kombinator U i Y z więcej niż 1 parametrem
W powyższych przykładach widzieliśmy, jak możemy zapętlić i przekazać argument, aby śledzić „stan” naszych obliczeń. Ale co, jeśli musimy śledzić dodatkowy stan?
My mogli korzystać z danych złożonych jak tablica lub coś ...
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
Ale to jest złe, ponieważ ujawnia stan wewnętrzny (liczniki ai b). Byłoby miło, gdybyśmy mogli po prostu zadzwonić, fibonacci (7)aby uzyskać odpowiedź, której szukamy.
Korzystając z tego, co wiemy o funkcjach curried (sekwencje funkcji jednoargumentowych (1-paramter)), możemy łatwo osiągnąć nasz cel bez konieczności modyfikowania naszej definicji Ydanych złożonych lub zaawansowanych funkcji językowych lub polegania na nich.
Przyjrzyj się fibonaccibliżej definicji poniżej. Natychmiast aplikujemy 0i 1które są zobowiązane do ai bodpowiednio. Teraz fibonacci po prostu czeka na podanie ostatniego argumentu, do którego zostanie przypisany x. Kiedy powtarzamy, musimy wywołać f (a) (b) (x)(nie f (a,b,x)), ponieważ nasza funkcja ma postać curry.
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
Ten rodzaj wzorca może być przydatny do definiowania różnego rodzaju funkcji. Poniżej widzimy dwie kolejne funkcje zdefiniowane za pomocą Ysyntezatora ( rangei reduce) i pochodną 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 ]
TO WSZYSTKO ANONIMOWE OMG
Ponieważ pracujemy tutaj z czystymi funkcjami, możemy zastąpić dowolną nazwaną funkcję jej definicją. Zobacz, co się dzieje, gdy bierzemy Fibonacciego i zastępujemy nazwane funkcje ich wyrażeniami
/* 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
I fibonacci (7)gotowe - wyliczane rekurencyjnie, używając wyłącznie funkcji anonimowych