Kombinator Y to koncepcja informatyki z „funkcjonalnej” strony rzeczy. Większość programistów w ogóle nie wie dużo o kombinatorach, jeśli nawet o nich słyszała.
- Co to jest kombinator Y?
- Jak działają kombinatory?
- Do czego są dobre?
- Czy są przydatne w językach proceduralnych?
functional-programming
computer-science
theory
definition
combinators
Chris Ammerman
źródło
źródło
Odpowiedzi:
Jeśli jesteś gotowy na długą lekturę, Mike Vanier ma świetne wytłumaczenie . Krótko mówiąc, pozwala na implementację rekurencji w języku, który niekoniecznie obsługuje go natywnie.
źródło
Kombinator Y to „funkcjonalna” (funkcja, która działa na inne funkcje), która umożliwia rekurencję, gdy nie można odnieść się do tej funkcji od wewnątrz. W teorii informatyki uogólnia ona rekurencję , wyabstrahowuje jej implementację, a tym samym oddziela ją od rzeczywistej pracy danej funkcji. Korzyści z braku potrzeby używania nazwy czasu kompilacji dla funkcji rekurencyjnej są swego rodzaju premią. =)
Dotyczy to języków obsługujących funkcje lambda . Wyrażenie -na charakter lambdas zwykle oznacza, że nie mogą one odnosić się do siebie po imieniu. A obejście tego poprzez zadeklarowanie zmiennej, odwołanie się do niej, a następnie przypisanie jej lambda, w celu uzupełnienia pętli odniesienia, jest kruche. Zmienną lambda można skopiować, a pierwotną zmienną przypisać ponownie, co przerywa samodzielne odniesienie.
Kombinatory Y są kłopotliwe we wdrażaniu i często stosowaniu w językach o typie statycznym (którymi często są języki proceduralne ), ponieważ zwykle ograniczenia w pisaniu wymagają liczby argumentów, aby dana funkcja była znana w czasie kompilacji. Oznacza to, że kombinator y musi być napisany dla każdej liczby argumentów, której należy użyć.
Poniżej znajduje się przykład użycia i działania Y-Combinatora w języku C #.
Korzystanie z kombinatora Y wymaga „nietypowego” sposobu konstruowania funkcji rekurencyjnej. Najpierw musisz napisać swoją funkcję jako fragment kodu, który wywołuje wcześniej istniejącą funkcję, a nie samą:
Następnie zamieniasz to w funkcję, która wywołuje funkcję, i zwraca funkcję, która to robi. Nazywa się to funkcjonalnym, ponieważ bierze jedną funkcję i wykonuje z nią operację, która skutkuje inną funkcją.
Teraz masz funkcję, która przyjmuje funkcję i zwraca inną funkcję, która wygląda jak silnia, ale zamiast wywoływać siebie, wywołuje argument przekazany do funkcji zewnętrznej. Jak to uczynić silnym? Przekaż wewnętrzną funkcję sobie. Y-Combinator robi to, będąc funkcją o stałej nazwie, która może wprowadzić rekurencję.
Zamiast samego wywołania czynnikowego dzieje się tak, że czynnikowe wywołuje generator czynnikowy (zwracany przez wywołanie rekurencyjne do kombinatora Y). I w zależności od bieżącej wartości t funkcja zwrócona z generatora albo wywoła generator ponownie, z t - 1, albo po prostu zwróci 1, kończąc rekurencję.
Jest to skomplikowane i tajemnicze, ale wszystko trzęsie się w czasie wykonywania, a kluczem do jego działania jest „odroczenie wykonania” i rozbicie rekurencji na dwie funkcje. Wewnętrzne F jest przekazywane jako argument , który należy wywołać w następnej iteracji, tylko w razie potrzeby .
źródło
fix :: (a -> a) -> a
a toa
z kolei może być funkcją dowolnej liczby argumentów. Oznacza to, że wpisywanie statyczne tak naprawdę nie jest uciążliwe.Podniosłem to z http://www.mail-archive.com/[email protected]/msg02716.html, co jest wyjaśnieniem, które napisałem kilka lat temu.
W tym przykładzie użyję JavaScript, ale wiele innych języków również będzie działać.
Naszym celem jest możliwość napisania funkcji rekurencyjnej 1 zmiennej przy użyciu tylko funkcji 1 zmiennej i bez przypisań, definiowania rzeczy po nazwie itp. (Dlaczego to jest naszym celem jest kolejne pytanie, weźmy to za wyzwanie, które my są podane.) Wydaje się niemożliwe, prawda? Jako przykład zastosujmy silnię.
Cóż, krok 1 to powiedzieć, że moglibyśmy to zrobić z łatwością, gdybyśmy trochę oszukali. Używając funkcji 2 zmiennych i przypisania, możemy przynajmniej uniknąć konieczności użycia przypisania w celu ustawienia rekurencji.
Zobaczmy teraz, czy możemy oszukiwać mniej. Po pierwsze, korzystamy z przydziału, ale nie musimy. Możemy po prostu napisać X i Y w wierszu.
Ale używamy funkcji 2 zmiennych, aby uzyskać funkcję 1 zmiennej. Czy możemy to naprawić? Cóż, inteligentny facet o imieniu Haskell Curry ma fajną sztuczkę, jeśli masz dobre funkcje wyższego rzędu, potrzebujesz tylko funkcji 1 zmiennej. Dowód jest taki, że można uzyskać z funkcji 2 (lub więcej w ogólnym przypadku) zmiennych do 1 zmiennej z czysto mechaniczną transformacją tekstu, taką jak ta:
gdzie ... pozostaje dokładnie takie samo. (Ta sztuczka nazywa się „curry” po jej wynalazcy. Język Haskell jest również nazwany po Haskell Curry. Plik, który w ramach bezużytecznych drobiazgów.) Teraz zastosuj tę transformację wszędzie i otrzymamy naszą ostateczną wersję.
Spróbuj tego. alert (), który powraca, przywiąż go do przycisku, cokolwiek. Ten kod oblicza silnię, rekurencyjnie, bez użycia przypisania, deklaracji lub funkcji 2 zmiennych. (Ale próba prześledzenia, jak to działa, prawdopodobnie spowoduje, że twoja głowa się zakręci. A wręczenie go, bez wyprowadzenia, tylko nieznacznie sformatowane spowoduje kod, który z pewnością zaskoczy i pomyli.)
4 linie, które rekurencyjnie definiują silnię, możesz zastąpić dowolną inną funkcją rekurencyjną.
źródło
function (n) { return builder(builder)(n);}
zamiastbuilder(builder)
?Zastanawiam się, czy może się przydać próba zbudowania tego od podstaw. Zobaczmy. Oto podstawowa, rekurencyjna funkcja silnia:
Refaktoryzujmy i stwórzmy nową funkcję o nazwie,
fact
która zwraca anonimową funkcję obliczania czynnikowego zamiast wykonywania samego obliczenia:To trochę dziwne, ale nie ma w tym nic złego. Po prostu generujemy nową funkcję silni na każdym kroku.
Rekurencja na tym etapie jest nadal dość wyraźna.
fact
Funkcja musi być świadomy własnej nazwy. Sparametryzujmy wywołanie rekurencyjne:To świetnie, ale
recurser
wciąż musi znać swoją nazwę. Sparametryzujmy to również:Teraz zamiast wywoływać
recurser(recurser)
bezpośrednio, utwórzmy funkcję otoki, która zwraca wynik:Teraz możemy
recurser
całkowicie pozbyć się nazwy; to tylko argument wewnętrznej funkcji Y, którą można zastąpić samą funkcją:Jedyną zewnętrzną nazwą, do której wciąż się odwołujemy, jest
fact
jednak jasne, że można to również łatwo sparametryzować, tworząc kompletne, ogólne rozwiązanie:źródło
recurser
. Nie ma najmniejszego pojęcia, co robi ani dlaczego.recurser
funkcja jest pierwszym krokiem do osiągnięcia tego celu, ponieważ daje nam rekurencyjną wersję,fact
która nigdy nie odwołuje się do nazwy.function Y(recurse) { return recurse(recurse); } let factorial = Y(creator => value => { return value == 0 ? 1 : value * creator(creator)(value - 1); });
. I tak to trawię (nie jestem pewien, czy jest poprawny): Nie odwołując się jawnie do funkcji (niedozwolonej jako kombinator ), możemy użyć dwóch częściowo zastosowanych / curry funkcji (funkcja twórcy i funkcja obliczania), z które możemy stworzyć funkcje lambda / anonimowe, które osiągają rekurencję bez potrzeby określania nazwy funkcji obliczania?Większość z powyższych odpowiedzi opisać Y combinator jest jednak nie to, co jest dla .
Operator paradoksalny są używane, aby pokazać, że rachunek lambda jest turing kompletna . Jest to bardzo ważny wynik w teorii obliczeń i zapewnia teoretyczne podstawy programowania funkcjonalnego .
Badanie kombinacji punktów stałych pomogło mi również naprawdę zrozumieć programowanie funkcjonalne. Jednak nigdy nie znalazłem dla nich żadnego zastosowania w rzeczywistym programowaniu.
źródło
y-combinator w JavaScript :
Edytować : Dużo się uczę patrząc na kod, ale ten jest trudny do przełknięcia bez odrobiny tła - przepraszam za to. Mając pewną ogólną wiedzę przedstawioną w innych odpowiedziach, możesz zacząć rozróżniać, co się dzieje.
Funkcja Y jest „kombinatorem y”. Teraz spójrz na
var factorial
linię, w której używane jest Y. Zauważ, że przekazujesz do niej funkcję, która ma parametr (w tym przykładzierecurse
), który jest również używany później w funkcji wewnętrznej. Nazwa parametru w zasadzie staje się nazwą funkcji wewnętrznej, pozwalając jej na wywołanie rekurencyjne (ponieważ używarecurse()
w swojej definicji). Kombinator y wykonuje magię powiązania anonimowej funkcji wewnętrznej z nazwą parametru funkcji przekazanej do Y.Aby uzyskać pełne wyjaśnienie, w jaki sposób Y wykonuje magię, sprawdź linkowany artykuł (nie przeze mnie).
źródło
arguments.callee
nie jest dozwolone w trybie ścisłym: developer.mozilla.org/en/JavaScript/…(function fact(n){ return n <= 1? 1 : n * fact(n-1); })(5)
Dla programistów, którzy nie zetknęli się z programowaniem funkcjonalnym dogłębnie i nie chcą zaczynać od razu, ale są raczej ciekawi:
Kombinator Y to formuła, która pozwala zaimplementować rekurencję w sytuacji, gdy funkcje nie mogą mieć nazw, ale mogą być przekazywane jako argumenty, używane jako wartości zwracane i definiowane w ramach innych funkcji.
Działa, przekazując funkcję do siebie jako argument, dzięki czemu może się wywoływać.
Jest to część rachunku lambda, który jest w rzeczywistości matematyką, ale w rzeczywistości jest językiem programowania i ma podstawowe znaczenie dla informatyki, a zwłaszcza programowania funkcjonalnego.
Codzienna praktyczna wartość kombinacji Y jest ograniczona, ponieważ języki programowania pozwalają na nazywanie funkcji.
W przypadku konieczności zidentyfikowania go w składzie policji wygląda to tak:
Zwykle można to zauważyć ze względu na powtarzające się
(λx.f (x x))
.Te
λ
symbole są grecką literą lambda, co daje rachunek lambda jego nazwy, a tam jest dużo(λx.t)
pod względem stylu, bo to, co wygląda jak lambda nazębnego.źródło
U x = x x
,Y = U . (. U)
(nadużywanie notacji podobnej do Haskella). IOW, z odpowiednimi kombinatorów,Y = BU(CBU)
. Tak więcYf = U (f . U) = (f . U) (f . U) = f (U (f . U)) = f ((f . U) (f . U))
.Anonimowa rekurencja
Kombinator stałoprzecinkowy jest funkcją wyższego rzędu,
fix
która z definicji spełnia równoważnośćfix f
przedstawia rozwiązaniex
równania stałego punktuSilnia liczby naturalnej można udowodnić za pomocą
Używanie
fix
dowolnych konstruktywnych dowodów na funkcje ogólne / μ-rekurencyjne można uzyskać bez nonimowej autoreferencyjności.gdzie
takie, że
Ten formalny dowód na to
metodycznie używa równoważnika kombinatora stałoprzecinkowego do przepisywania
Rachunek Lambda
Bez typu rachunek lambda formalizm polega na gramatyki bezkontekstowych
gdzie
v
rozciąga się na zmienne, wraz z regułami redukcji beta i etaRedukcja beta zastępuje wszystkie wolne wystąpienia zmiennej
x
w ciele abstrakcji („funkcja”)B
wyrażeniem („argument”)E
. Redukcja Eta eliminuje zbędną abstrakcję. Czasem jest to pomijane w formalizmie. Irreducible wypowiedzi, do których ma zastosowanie reguła nie redukcja, jest w normalnym lub postaci kanonicznej .jest skrótem od
(różnorodność abstrakcyjna),
jest skrótem od
(lewe skojarzenie aplikacji),
i
są równoważne alfa .
Abstrakcja i zastosowanie to dwa jedyne „prymitywy językowe” rachunku lambda, ale umożliwiają kodowanie dowolnie złożonych danych i operacji.
Cyfry kościelne są kodowaniem liczb naturalnych podobnych do Peano-aksomatycznych naturałów.
Formalny dowód na to
używając reguły przepisywania redukcji wersji beta:
Kombinatory
W rachunku lambda kombinatory są abstrakcjami, które nie zawierają wolnych zmiennych. Najprościej:
I
kombinator tożsamościizomorficzny do funkcji tożsamości
Takie kombinatory są prymitywnymi operatorami rachunku kombinatorycznego, takimi jak system SKI.
Redukcja beta nie jest silnie normalizująca ; nie wszystkie redukowalne wyrażenia, „redeksy”, zbiegają się do normalnej formy przy redukcji beta. Prostym przykładem jest rozbieżne zastosowanie
ω
kombinatora omegaDo siebie:
Priorytetem jest redukcja skrajnych lewych podwyrażeń („głów”). Kolejność stosowania normalizuje argumenty przed podstawieniem, normalna kolejność nie. Dwie strategie są analogiczne do chętnej oceny, np. C, i leniwej oceny, np. Haskell.
rozbiega się pod niecierpliwą redukcją wersji beta zamówień
ponieważ w ścisłej semantyce
ale zbiega się pod leniwą redukcją beta normalnego rzędu
Jeśli wyrażenie ma normalną formę, odnajdzie ją redukcja beta normalnego rzędu.
Y
Zasadnicza właściwość
Y
kombinatora stałoprzecinkowegojest dany przez
Równoważność
jest izomorficzny
Rachunek lambda bez typu może kodować arbitralne konstruktywne dowody nad funkcjami ogólnymi / μ-rekurencyjnymi.
(Mnożenie opóźnione, konfluencja)
W przypadku kościelnego rachunku lambda bez typu wykazano, że istnieje poza tym rekurencyjnie wyliczalna nieskończoność kombinatorów stałoprzecinkowych
Y
.Redukcja beta w normalnym rzędzie sprawia, że nierozwinięty niepoprawny rachunek lambda jest kompletnym systemem przepisywania Turinga.
W Haskell kombinator stałoprzecinkowy można elegancko zaimplementować
Leniwość Haskella normalizuje się do skończoności, zanim wszystkie podwyrażenia zostaną ocenione.
źródło
λ x . x
, jak się masz?Inne odpowiedzi dostarczają dość zwięzłej odpowiedzi na to pytanie, bez jednego ważnego faktu: nie musisz implementować kombinatora punktów stałych w żadnym praktycznym języku w ten zawiły sposób, a to nie ma żadnego praktycznego celu (oprócz „patrz, wiem, co to jest kombinator Y jest"). To ważna koncepcja teoretyczna, ale mało praktyczna.
źródło
Oto implementacja JavaScript funkcji Y-Combinator i funkcji Factorial (z artykułu Douglasa Crockforda, dostępnego pod adresem: http://javascript.crockford.com/little.html ).
źródło
Kombinator Y to inna nazwa kondensatora topnikowego.
źródło
Napisałem coś w rodzaju „idiotycznego przewodnika” po Y-Combinatorze zarówno w Clojure, jak i Schemacie, aby pomóc sobie z tym poradzić. Wpływają na nie materiały z „The Little Schemer”
W schemacie: https://gist.github.com/z5h/238891
lub Clojure: https://gist.github.com/z5h/5102747
Oba samouczki są przeplatane kodem z komentarzami i powinny być wycięte i wklejone do twojego ulubionego edytora.
źródło
Jako nowicjusz w kombinatorach znalazłem artykuł Mike'a Vaniera (dzięki Nicholas Mancuso) za bardzo pomocny. Chciałbym napisać streszczenie, oprócz dokumentowania mojego zrozumienia, jeśli byłoby to pomocne dla innych, byłbym bardzo zadowolony.
Od Crappy do mniej Crappy
Na przykładzie silni używamy następującej
almost-factorial
funkcji do obliczania silni liczbyx
:W powyższym pseudokodzie
almost-factorial
przyjmuje funkcjęf
i liczbęx
(almost-factorial
jest curry, więc może być postrzegane jako przejmowanie funkcjif
i zwracanie funkcji 1-arianowej).Kiedy
almost-factorial
oblicza silnię dlax
, deleguje obliczenie silni dlax - 1
dof
i kumuluje ten wynik zx
(w tym przypadku mnoży wynik (x - 1) przez x).Można zauważyć, jak
almost-factorial
odbywa się w brzydko wersją funkcji silni (który można obliczyć tylko do numerux - 1
) i zwraca mniej brzydko wersję silnia (który oblicza numer kasyx
). Jak w tej formie:Jeśli wielokrotnie przekażemy mniej nieprzyjemną wersję silni
almost-factorial
, ostatecznie uzyskamy pożądaną funkcję silnif
. Gdzie można to uznać za:Punkt stały
To, co
almost-factorial f = f
oznacza,f
jest stałym punktem funkcjialmost-factorial
.To był naprawdę interesujący sposób, aby zobaczyć związki powyższych funkcji i był to dla mnie moment aha. (jeśli nie, przeczytaj post Mike'a w punkcie naprawy)
Trzy funkcje
Uogólniając, mamy funkcję nierekurencyjną
fn
(taką jak nasza prawie czynnikowa), mamy jej funkcję punktu stałegofr
(jak nasze f), a następnie to, co sięY
dzieje, gdy dajeszY
fn
,Y
zwraca funkcję punktu stałego zfn
.Tak w skrócie (uproszczonej zakładając
fr
trwa tylko jeden parametr;x
degeneratów sięx - 1
,x - 2
... w rekursji):fn
:def fn fr x = ...accumulate x with result from (fr (- x 1))
jest to niemal przydatna funkcja - mimo, że nie można użyćfn
bezpośredniox
, to będzie przydatny bardzo szybko. Ta nierekurencyjnafn
używa funkcjifr
do obliczenia swojego wynikufn fr = fr
,fr
To fix-punktfn
,fr
jest przydatna funciton, możemy użyćfr
nax
dostawać nasz wynikY fn = fr
,Y
zwraca punkt stały funkcji,Y
zamienia naszą prawie użyteczną funkcjęfn
w przydatnąfr
Pochodzenie
Y
(brak w zestawie)Pominę wyprowadzenie
Y
i przejdę do zrozumieniaY
. Post Mike'a Vainera zawiera wiele szczegółów.Forma
Y
Y
jest zdefiniowany jako (w formacie rachunku lambda ):Jeśli zmienimy zmienną
s
po lewej stronie funkcji, otrzymamyRzeczywiście, wynikiem
(Y f)
jest punkt stałyf
.Dlaczego
(Y f)
działaW zależności od podpisu
f
,(Y f)
może być funkcją dowolnego arsena, aby uprościć, załóżmy, że przyjmuje(Y f)
tylko jeden parametr, taki jak nasza funkcja silnia.ponieważ
fn fr = fr
kontynuujemyobliczenia rekurencyjne kończą się, gdy najbardziej wewnętrzny
(fn fr 1)
jest przypadek podstawowy ifn
nie jest używanyfr
w obliczeniach.Patrząc
Y
ponownie:Więc
Dla mnie magiczne części tego zestawu to:
fn
ifr
współzależą od siebie:fr
„zawija się” wfn
środku, za każdym razem, gdyfr
używa się do obliczeńx
, „spawnuje” („unosi”?)fn
i przekazuje obliczenia do tegofn
(przechodząc w siebiefr
ix
); z drugiej stronyfn
zależyfr
i wykorzystujefr
do obliczenia wyniku mniejszego problemux-1
.fr
jest używany do definiowaniafn
(kiedyfn
używafr
w swoich operacjach), rzeczywistyfr
nie jest jeszcze zdefiniowany.fn
określa prawdziwą logikę biznesową. Na podstawiefn
,Y
tworzyfr
- funkcja pomocnika w postaci konkretnej - w celu ułatwienia obliczenia dlafn
w rekurencyjnego sposób.W
Y
tej chwili pomogło mi to zrozumieć , mam nadzieję, że to pomoże.BTW, znalazłem również książkę Wprowadzenie do programowania funkcjonalnego za pomocą rachunku lambda bardzo dobrze, jestem tylko jej częścią, a fakt, że nie mogłem się skupić
Y
na książce, doprowadził mnie do tego postu.źródło
Oto odpowiedzi na oryginalne pytania zebrane z artykułu (który TOTALY wart jest przeczytania) wymienionego w odpowiedzi Nicholasa Mancuso , a także inne odpowiedzi:
Kombinator Y to „funkcjonalna” (lub funkcja wyższego rzędu - funkcja działająca na innych funkcjach), która pobiera pojedynczy argument, który nie jest rekurencyjny i zwraca wersję funkcji, która jest rekurencyjny.
Nieco rekurencyjne =), ale bardziej szczegółowa definicja:
Kombinator - to po prostu wyrażenie lambda bez wolnych zmiennych.
Wolna zmienna - jest zmienną, która nie jest zmienną powiązaną.
Zmienna związana - zmienna zawarta w treści wyrażenia lambda, która ma tę nazwę zmiennej jako jeden z jej argumentów.
Innym sposobem myślenia o tym jest to, że kombinator jest takim wyrażeniem lambda, w którym możesz zastąpić nazwę kombinatora definicją wszędzie tam, gdzie zostanie znaleziona i mieć wszystko nadal działające (dostaniesz się w nieskończoną pętlę, jeśli kombinator zawierają odniesienie do siebie w ciele lambda).
Kombinator Y to kombinator stałoprzecinkowy.
Stały punkt funkcji jest elementem domeny funkcji, który jest odwzorowany na funkcję przez funkcję.
To znaczy,
c
jest stałym punktem funkcji,f(x)
jeślif(c) = c
to oznacza
f(f(...f(c)...)) = fn(c) = c
Poniższe przykłady zakładają mocne + dynamiczne pisanie:
Leniwy (normalny) kombinator Y:
Ta definicja ma zastosowanie do języków z leniwą (także: odroczoną, wywołaną potrzebą) oceną - strategią oceny, która opóźnia ocenę wyrażenia, dopóki jego wartość nie będzie potrzebna.
Oznacza to, że dla danej funkcji
f
(która jest funkcją nierekurencyjną) odpowiednią funkcję rekurencyjną można uzyskać najpierw przez obliczenieλx.f(x x)
, a następnie zastosowanie do niej tego wyrażenia lambda.Ścisły (w kolejności aplikacyjnej) kombinator Y:
Definicja ta dotyczy języków z surową (także: chętną, chciwą) oceną - strategią oceny, w której wyrażenie jest oceniane, gdy tylko zostanie powiązane ze zmienną.
Z natury jest taki sam, jak leniwy, ma tylko dodatkowe
λ
opakowania, które opóźniają ocenę ciała lambdy. Zadałem kolejne pytanie , nieco związane z tym tematem.Skradzionyzapożyczony z odpowiedzi Chrisa Ammermana : Kombinator Y uogólnia rekurencję, wyodrębnia jej implementację, a tym samym oddziela ją od faktycznej pracy danej funkcji.Mimo że Y-combinator ma kilka praktycznych zastosowań, jest to głównie koncepcja teoretyczna, której zrozumienie poszerzy ogólną wizję i prawdopodobnie zwiększy twoje umiejętności analityczne i programistyczne.
Jak stwierdził Mike Vanier : możliwe jest zdefiniowanie kombinatora Y w wielu statycznie typowanych językach, ale (przynajmniej w przykładach, które widziałem) takie definicje zwykle wymagają nieoczywistego hakowania, ponieważ sam kombinator Y nie robi mają prosty typ statyczny. To wykracza poza zakres tego artykułu, więc nie będę o tym więcej wspominał
I jak wspomniał Chris Ammerman : większość języków proceduralnych ma typowanie statyczne.
Więc odpowiedz na to pytanie - niezupełnie.
źródło
Kombinator y implementuje anonimową rekurencję. Więc zamiast
możesz to zrobić
oczywiście kombinator y działa tylko w językach nazw według nazw. Jeśli chcesz użyć tego w dowolnym normalnym języku call-by-value, będziesz potrzebować powiązanego z-combinatora (y-combinator będzie się rozchodził / pętla nieskończona).
źródło
Kombinator stałoprzecinkowy (lub operator stałoprzecinkowy) to funkcja wyższego rzędu, która oblicza stały punkt innych funkcji. Ta operacja jest istotna w teorii języka programowania, ponieważ pozwala na implementację rekurencji w formie reguły przepisywania, bez wyraźnego wsparcia ze strony silnika wykonawczego języka. (src Wikipedia)
źródło
Ten operator może uprościć Ci życie:
Następnie unikasz dodatkowej funkcji:
Wreszcie dzwonisz
fac(5)
.źródło
Myślę, że najlepszym sposobem na to jest wybór języka, takiego jak JavaScript:
Teraz przepisz go, aby nie używał nazwy funkcji wewnątrz funkcji, ale nadal wywoływał ją rekurencyjnie.
Jedyne miejsce, w którym nazwa funkcji
factorial
powinna być widoczna, to strona wywoływania.Wskazówka: nie możesz używać nazw funkcji, ale możesz używać nazw parametrów.
Rozwiąż problem. Nie patrz w górę. Gdy go rozwiążesz, zrozumiesz, jaki problem rozwiązuje kombinator y.
źródło