Wstęp
W latach trzydziestych XX wieku Alonzo Church i Haskell Curry,
pracując niezależnie, opracowali notację dla funkcji — rachunek
lambda. Spojrzenie na funkcje w rachunku lambda jest nieco inne od
klasycznego, teoriomnogościowego rozumienia funkcji jako zbioru par
argument-wartość. W rachunku lambda funkcje rozumiane są jako
przepisy, aby podkreślić ich obliczalność. Rachunek lambda ma dwa zasadnicze warianty: nietypowany, zwany po prostu
rachunkiem lambda, oraz typowany. Najpierw przedstawimy nieco obszerniej
ten pierwszy; później powiemy krótko o rachunku z typami.
Podstawowe definicje i własności
Napisy w rachunku lambda nazywamy termami. Zbiór
termów
rachunku lambda konstruuje się indukcyjnie jako zbiór słów nad alfabetem
, gdzie
jest przeliczalnym zbiorem ziennych.
Do zbioru
należą tylko termy skonstruowane w poniższy sposób:
- Dowolna zmienna ze zbioru
jest termem rachunku lambda, czyli jeżeli
, to
.
- Jeżeli weźmiemy dowolny term
ze zbioru
, oraz dowolną zmienną
ze zbioru
, to term postaci
jest termem rachunku lambda, czyli jeżeli
i
to
. Powyższy sposób tworzenia termu nazywamy abstrakcją.
- Inna metoda to metoda aplikacji, czyli jeżeli termy
i
to
.
Nawiasy są często pomijane według zasady łączności lewostronnej: na
przykład
Wieloargumentowe funkcje są
reprezentowane przez funkcje jednoargumentowe, których argumentami
są funkcje. Zatem to, co zwykle jest zapisywane w postaci
,
w rachunku lambda zapisuje się
Powtórzenia
symbolu
pomijane są według zasady łączności prawostronnej,
tzn.
W termie rachunku lambda zmienna może być wolna albo związana. Wolnym wystapieniem zmiennej
nazywamy tylko takie wystąpienie
tej zmiennej, które nie jest „w zasięgu” żadnego
.
Zbiór zmiennych wolnych termu
,
oznaczany przez
, zdefiniowany jest, w zależności od budowy termu
,
w następujący sposób:
- Jeżeli
jest pojedynczą zmienną, tzn.
, to zmienna ta jest wolna, a zatem
.
- Jeżeli
jest postaci
, to
wiąże wszystkie wolne wystąpienia zmiennej
w termie
, a zatem
.
- Jeżeli
jest postaci
, to zmiennymi wolnymi termu
są wszystkie zmienne wolne występujące w termach
i
, czyli 
Zmienna jest zmienną wolną termu
, jeżeli należy do
zbioru
a zmienną związaną w przeciwnym wypadku.
Term
jest domknięty wtedy i tylko wtedy, gdy
Przykład
- W termie
zmienna
jest zmienną wolną, a
zmienną związaną. W termie
nie ma zmiennych wolnych, jest to więc term domknięty.
- W termie
tylko pierwsze wystąpienie zmiennej
jest wolne, chociaż
. Oczywiście term
nie jest termem domkniętym.
Na termy w rachunku lambda patrzymy jako na przepisy, jak z zadanej wartości argumentu otrzymać wartość funkcji; zatem termy to procedury, których parametry specyfikowane są poprzez lambda abstrakcję:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \lambda \underbrace{x_1... x_n}_{\mbox{\tiny parametry}}. \underbrace{M}_{\mbox{\tiny ciało procedury}}}
Alfa-konwersja i beta-redukcja, czyli obliczanie
Termy rachunku lambda możemy przekształcać, korzystając z
-konwersji
i
-redukcji.
-konwersja to po prostu zamiana nazw zmiennych
związanych.
Zdarza się, że na przykład poprzez stosowanie „podprocedur” lub
parametrów pochodzących z wykonania innych procedur dochodzi
do konfliktu nazw zmiennych. Aby zapewnić poprawne wykonanie,
stosujemy
-konwersję, która pozwala zapewnić
unikalność nazw.
gdzie
, a
oznacza wynik postawienia zmiennej
za każde wolne wystąpienie zmiennej
w termie
.
Będziemy używać oznaczenia
, aby powiedzieć, że
termy
i
są równe modulo
-konwersja, tzn.
że mogą być zredukowane do tego samego
termu dzięki zastosowaniu skończonej liczby
-konwersji.
Przykład
Niech
,
,
oznaczają różne zmienne. Wówczas
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned \lambda x.x & =_\alpha \lambda y.y\\ \lambda x.xz & =_\alpha \lambda y.yz\\ \lambda x.xy & \neq_{\alpha} \lambda x.xz. \end{align}}
-redukcja —
obliczenia wykonywane przez procedurę symulowane są w rachunku lambda
poprzez proces zwany
-redukcją. Aplikujemy procedurę
do argumentu
:
w taki sposób, że każda zmienna wolna termu
pozostaje wolna
po podstawieniu. Jest to oczywiście możliwe do osiągnięcia po
ewentualnym wcześniejszym zastosowaniu
-konwersji.
Na przykład:
jest nieprawidłowym użyciem
-konwersji, gdyż
dochodzi do konfliktu oznaczeń i do utraty pierwotnego
znaczenia termu-procedury. Jednakże można temu zaradzić, stosując
-konwersję:
Używamy oznaczenia
, aby powiedzieć, że
termy
i
są równe modulo
-konwersja, tzn.
że mogą być zredukowane do tego samego
termu dzięki zastosowaniu skończonej liczby
-konwersji.
Aplikację
nazywamy
-redeksem, jeżeli term
jest w postaci
-abstrakcji.
Term
jest w postaci normalnej, jeżeli nie
zawiera
-redeksów, tzn. żaden podterm termu
nie jest
-redeksem.
Własność Churcha-Rossera
Poniższe twierdzenie zwane jest własnością Churcha-Rossera lub własnością karo:
Jeżeli term
poprzez pewne ciągi
-redukcji redukuje się do termów
i
,
to istnieje term
taki, że zarówno
, jak i
można zredukować do
(poprzez ciągi
-redukcji):
Powyższa własność mówi, że postać normalna (o ile istnieje) jest unikalna
z dokładnością do
-konwersji.
Term
jest normalizowalny, jeżeli można go zredukować do postaci
normalnej, a jest silnie normalizowalny, jeżeli każda droga redukcji prowadzi do
postaci normalnej. Beztypowy rachunek lambda nie ma żadnej z tych własności.
Wprowadźmy oznaczenie:
. Najprostszym przykładem
termu nie posiadającego postaci normalnej jest
:
Przykładem termu, w którym nie każda droga redukcji
prowadzi do postaci normalnej, jest:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned \overbrace{(\lambda x.y) (\underbrace{\Delta\Delta}_{(1)})}^{(2)} & =_\beta (\lambda x.y)(\Delta\Delta) & =_\beta & \mbox{~~~~...} & \mbox{~~~~~~| redukując (1)}\\ & =_\beta y & & & \mbox{~~~~~~| redukując (2)} \end{align} }
Istnieją strategie redukcji, które zawsze doprowadzają do postaci normalnej,
oczywiście gdy postać normalna istnieje. Taką strategią jest na przykład
metoda redukowania zawsze najbardziej lewego redeksu.
Lambda-definiowalność funkcji na liczbach naturalnych
Do tej pory nie przedstawiliśmy żadnych termów oznaczających liczby
naturalne czy funkcje operujące na liczbach naturalnych, wydaje się
więc rzeczą dziwną mówienie o lambda reprezentowalności tych
funkcji. Okazuje się jednak, że w rachunku lambda nie potrzeba
żadnych dodatkowych oznaczeń: pojęcie liczby i funkcji daje się
wprowadzić na bazie do tej pory przyjętych definicji.
Popatrzmy na liczbę naturalną
jak na procedurę, która
-krotnie stosuje następnik do zera. Oczywiste jest, że taka
procedura w sposób jednoznaczny określa liczbę, i na odwrót.
Mając dany przepis wiemy, jaką liczbę naturalną liczy,
a mając liczbę naturalną doskonale wiemy, który przepis
zastosować.
- Term
reprezentuje liczbę naturalną 0.
- Term Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \lambda sx.\ \underbrace{s( s( ... (s}_{\mbox{\tiny n razy}}x) ...))}
reprezentuje liczbę naturalną
.
Term
to po prostu
algorytm: „weź następnik
, weź zero
i zwróć zero”; natomiast term Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \lambda sx.\ \underbrace{s( s( ... (s}_{\mbox{\tiny n razy}}x) ...))}
to algorytm:
„weź następnik
, weź zero
, zastosuj
-krotnie następnik do zera i zwróć wynik”.
Zatem liczba naturalna w naszym rozumieniu to procedura, która
ma dwa parametry:
(następnik) oraz
(zero).
Oczywiście powyższe termy podane są z dokładnością do
-konwersji. Np. term
reprezentuje 2, gdyż
.
Mówimy, że term
rachunku lambda reprezentuje funkcję na
liczbach naturalnych
wtedy
i tylko wtedy, gdy
, gdzie
oznacza reprezentację liczby
w rachunku lambda.
Operacje na liczbach
Znając reprezentację liczb naturalnych w rachunku lambda,
możemy zdefiniować podstawowe operacje na liczbach.
Term
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned A & = & \lambda mnsx. (ms)(nsx) \end{align}}
reprezentuje dodawanie.
Jest to procedura, która oczekuje na podanie
dwóch parametrów:
i
, a w wyniku daje procedurę
liczącą liczbę naturalną (będącą sumą liczb
i
).
Jak wygląda algorytm otrzymany w wyniku działania procedury
? Jest to algorytm, który
-krotnie stosuje następnik
do
.
Przykład
Zobaczmy, jak działa term
na liczbach 1 i 2:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned A\underline{1}\ \underline{2} & = & & \{[\lambda m.\ (\lambda n.\ (\lambda sx.\ (ms)(nsx))]\underline{1}\} \underline{2} & & \mbox{~~~~~~| dopisując nawiasy i } \lambda\\ & =_\beta & &[\lambda n.\ (\lambda sx.\ (\underline{2}s)(nsx)]\underline{1} & & \mbox{~~~~~~| } m := 2\\ & =_\beta & & \lambda sx.\ (\underline{2} s)(\underline{1} sx) & & \mbox{~~~~~~| } n := 1\\ & = & & \lambda sx.\ (\underline{2} s)\{[(\lambda t.\ (\lambda y.\ ty))s]x\} & & \mbox{~~~~~~| dopisując nawiasy i } \lambda\\ & =_\beta & & \lambda sx.\ (\underline{2} s)[(\lambda y.\ sy)x] & & \mbox{~~~~~~| } t := s\\ & =_\beta & & \lambda sx.\ (\underline{2} s)(sx) & & \mbox{~~~~~~| } y := x\\ & = & & \lambda sx.\ \{\underbrace{[ \lambda t.\ (\lambda y. t(ty))]}_{\mbox{\tiny procedura licząca 2}} s\} \underbrace{(sx)}_{\mbox{\tiny „zero”}} & & \mbox{~~~~~~| dopisując nawiasy i } \lambda\\ & =_\beta & & \lambda sx.\ (\lambda y.\ s(sy))(sx) & & \mbox{~~~~~~| } t := s\\ & =_\beta & & \lambda sx.\ s(s(sx)) & & \mbox{~~~~~~| } y := sx\\ & = & & \underline{3} \end{align}}
Term
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned M & = & \lambda mnsx. n(ms)x \end{align}}
reprezentuje mnożenie. Procedura mnożenia oczekuje na podanie dwóch argumentów
i
,
a w wyniku daje procedurę, która liczy liczbę naturalną
w sposób następujący:
-krotnie dodaje liczbę
, zaczynając
od zera. Liczba naturalna obliczona w taki sposób to
.
Przykład
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned M\underline{2}\ \underline{2} & = & & \{[\lambda m.\ (\lambda n.\ (\lambda sx.\ n(ms)x))]\underline{2}\} \underline{2} & & \mbox{~~~~~~| dopisując nawiasy i } \lambda\\ & =_\beta & & [\lambda n.\ (\lambda sx.\ n(\underline{2}s)x)]\underline{2} & & \mbox{~~~~~~| } m := 2\\ & =_\beta & & \lambda sx.\ \underline{2} (\underline{2} s)x & & \mbox{~~~~~~| } n := 2\\ & = & & \lambda sx.\ \underline{2} [\underbrace{(\lambda t.\ (\lambda y.\ t(ty)))}_{\mbox{\tiny procedura licząca 2}}s]x & & \mbox{~~~~~~| dopisując nawiasy i } \lambda\\ & =_\beta & & \lambda sx.\ \underline{2} [\lambda y.\ s(sy)] x & & \mbox{~~~~~~| } t := s\\ & = & & \lambda sx.\ \underbrace{[ \lambda t.\ (\lambda z. t(tz))]}_{\mbox{\tiny procedura licząca 2}} \underbrace{[\lambda y. s(sy)]}_{\mbox{\tiny dodawanie 2}} x & & \mbox{~~~~~~| dopisując nawiasy i } \lambda\\ & =_\beta & & \lambda sx.\ (\lambda z.\ [\lambda y. s(sy)] ([\lambda y. s(sy)]z))x & & \mbox{~~~~~~| } t := [\lambda y. s(sy)]\\ & =_\beta & & \lambda sx.\ [\lambda y. s(sy)] ([\lambda y. s(sy)]z) & & \mbox{~~~~~~| } z := x\\ & =_\beta & & \lambda sx.\ s(s[s(sx)]) & &\mbox{~~~~~~| } y := s(sx)\\ & = & & \underline{4} \end{align}}
Term
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned C & = & \lambda mnksx. m(\lambda y.nsx)(ksx) \end{align}}
reprezentuje funkcję
. Term
oczekuje na podanie trzech argumentów
i tak jak w poprzednich przykładach zwraca procedurę obliczającą
liczbę naturalną. W przypadku, gdy pierwszy parametr
jest
zerem, procedura wynikowa liczy liczbę
, w przeciwnym przypadku — liczbę
.
Przykład
Zobaczmy dwa przykłady działania termu
-- pierwszy, gdy
, oraz drugi, gdy
.
Przykład pierwszy
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned \mbox{} C \underline{0}\ \underline{n}\ \underline{k} & =_\beta & & \lambda sx.\ \underline{0} (\lambda y.\ \underline{n} sx) (\underline{k} sx)\\ & = & & \lambda sx.\ \underbrace{(\lambda tz. z)}_{\underline{0}} (\lambda y.\ \underline{n} sx)(\underline{k}sx) \\ & =_\beta & & \lambda sx.\ \underline{k} sx \\ & =_\beta & & \underline{k} \end{align}}
Przykład drugi
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned C \underline{m}\ \underline{n}\ \underline{k} & =_\beta & & \lambda sx. \underline{m} (\lambda y. \underline{n} sx) (\underline{k} sx) \\ & = & & \lambda sx.\ [\lambda tz.\ \underbrace{t(... (t}_{\tiny m\neq 0 \mbox{ razy}}y) ...)] (\lambda y.\ \underline{n} sx)(\underline{k} sx) \\ & =_\beta & & \lambda sx. [\lambda y.\ \underline{n} sx] (... ([\lambda y.\ \underline{n} sx](\underline{k} sx)) ...)\\ & =_\beta & & \lambda sx. \underline{n} sx \end{align}}
Przekazanie funkcji jako parametru
Musimy częściowo zapomnieć o naszych intuicjach, gdy spojrzymy na
term reprezentujący funkcję wykładniczą. Co prawda jest to term, który jak wszystkie poprzednie oczekuje na podanie (dwóch) parametrów, w wyniku daje procedurę liczącą liczbę naturalną, ale trudno tu dopatrzeć się analogii do tych języków programowania, które przekazują parametry tylko przez wartość.
Term
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned E & = & \lambda mn. (mn) \end{align}}
reprezentuje funkcję
i ma pewną
„niezwykłą” własność: w wyniku zwraca algorytm, który
„zero” jednego z parametrów traktuje jako następnik. Oto przykład:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned E\underline{2}\ \underline{2} & = & & [\lambda mn.\ (mn)] \underline{2}\ \underline{2}\\ & =_\beta & & \underline{2}\ \underline{2} & & \mbox{~~~~~~| } m := \underline{2},\ n := \underline{2}\\ & = & & \underbrace{[\lambda sx.\ s(sx)]}_{\underline{2}}\underline{2} &\\ & =_\beta & & \lambda x.\ \underline{2} (\underline{2} x) & & \mbox{~~~~~~| } s := \underline{2}\\ & =_\alpha & & \lambda s.\ \underline{2} (\underline{2} s) & & \mbox{~~~~~~| } x := s\\ & = & & \lambda s. [\lambda tx.\ t(tx)](\underline{2} s)\\ & =_\beta & & \lambda sx.\ (\underline{2} s)((\underline{2} s)x) & & \mbox{~~~~~~| } t := \underline{2} s\\ & = & & \lambda sx.\ (\underbrace{[\lambda ty. t(ty)]}_{\underline{2}}s) ((\underbrace{[\lambda pz. p(pz)]}_{\underline{2}}s)x)\\ & =_\beta & & \lambda sx.\ [\lambda y.\ s(sy)](s(sx)) & & \mbox{~~~~~~| } t := s,\ p := s,\ z := x\\ & =_\beta & & \lambda sx.\ s(s[s(sx)]) & & \mbox{~~~~~~| } y := s(sx)\\ & = & & \underline{4} \end{align} }
Ciekawym termem jest też term reprezentujący poprzednik:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned P & = & \lambda n. (n (\lambda z. <\!\! A\underline{1} (z\underline{0}), (z\underline{0})\!\!>) <\!\!\underline{0}, \underline{0}\!\!>) \underline{1}, \end{align}}
gdzie
oznacza wspominany już wcześniej term dodawania,
jest parą uporządkowaną, której pierwsza składowa to
, natomiast druga to
. Jaką procedurę opisuje term
? Mówi on tyle: „weź liczbę naturalną
, a następnie
razy wykonaj algorytm weź parę
i utwórz nową parę
, startując od pary
; jako wynik zwróć drugą składową ostatnio otrzymanej pary”. Uwaga: term rerprezentujący parę uporządkowaną zapisujemy tu w nawiasach kątowych, by odróżnić je od nawiasów grupujących fragmenty termów.
Wygląda to następująco:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned <0, 0> & \mbox{~}\\ <1, 0> & \mbox{~~~~~~krok 1}\\ <2, 1> & \mbox{~~~~~~{krok 2}}\\ ... \\ <n, n-1> & \mbox{~~~~~~krok } n \end{align} }
Zatem term
definiuje funkcję
, gdzie
.
Przykład
Zobaczmy teraz, jak działa term opisujący powyższą procedurę.
Policzmy:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned P\underline{2} & = & & \{\lambda n. (n (\lambda z. <\!\!A\underline{1} (z\underline{0}), (z\underline{0}) \!\!>) <\!\!\underline{0}, \underline{0}\!\!>)\underline{1}\}\underline{2}\\ & =_\beta & & (\underline{2} (\lambda z. <\!\! A\underline{1} (z\underline{0}), (z\underline{0}) \!\!> ) <\!\!\underline{0}, \underline{0}\!\!> )\underline{1}\\ & = & & ((\underbrace{\lambda sx.\ s(sx)}_{\mbox{\tiny procedura licząca 2}}) (\underbrace{\lambda z. <\!\!A\underline{1}(z\underline{0}), (z\underline{0}) \!\!>}_{\mbox{\tiny następnik}}) \underbrace{<\!\!\underline{0}, \underline{0}\!\!>}_{\mbox{\tiny zero}}) \underline{1}\\ & =_\beta & & ((\lambda z. <\!\! A\underline{1} (z\underline{0}), (z\underline{0}) \!\!> ) [(\lambda z. <\!\! A\underline{1} (z\underline{0}), (z\underline{0}) \!\!> ) <\!\! \underline{0}, \underline{0} \!\!>])\underline{1}\\ & =_\beta & & ((\lambda z. <\!\! A\underline{1} (z\underline{0}), (z\underline{0}) \!\!> ) <\!\! A\underline{1} \underline{0}, \underline{0} \!\!> )\underline{1}\\ & =_\beta & & ((\lambda z. <\!\! A\underline{1} (z\underline{0}), (z\underline{0}) \!\!> ) <\!\! \underline{1}, \underline{0} \!\!> )\underline{1}\\ & =_\beta & & ( <\!\! A\underline{1} \underline{1}, \underline{1}\!\!> ) \underline{1}\\ & =_\beta & & ( <\!\! \underline{2}, \underline{1}\!\!> ) \underline{1}\\ & =_\beta & & \underline{1} \end{align} }
Operator punktu stałego
Podaliśmy tutaj tylko przykłady funkcji reprezentowalnych w rachunku
lambda. Okazuje się, że beztypowy rachunek lambda to model obliczeń
równoważny funkcjom rekurencyjnym (obliczalnym). Oznacza to,
że każdą funkcję obliczalną można reprezentować w rachunku lambda
oraz że każdy term rachunku lambda reprezentuje pewną funkcję
obliczalną.
Aby móc dowolną funkcję rekurencyjną reprezentować w rachunku lambda, konieczne jest wprowadzenie operatora punktu stałego
. Operator punktu stałego to term, który dla dowolnego termu
daje jego punkt stały, tzn.
Zauważmy, że term
jest oczekiwanym operatorem. Zobaczmy, jak term
zachowuje
się zaaplikowany do dowolnego termu
:
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned {\cal Y} M & = & &( \lambda y. (\lambda x. y(xx))(\lambda x. y(xx)))M & & \mbox{~~~~~~| z definicji } {\cal Y} \\ & =_\beta & & (\lambda x. M(xx))\underbrace{(\lambda x. M(xx))}_B & & \mbox{~~~~~~| } y := M\\ & =_\beta & & M(\underbrace{(\lambda x. M(xx))}_B \underbrace{(\lambda x. M(xx))}_B ) & & \mbox{~~~~~~| } x := B\\ & =_\beta & & M( {\cal Y} M ) & & \mbox{~~~~~~| } {\cal Y} M = (\lambda x. M(xx))(\lambda x. M(xx))\\ \end{align} }
Typowany rachunek lambda
Znaczenie typowanego rachunku lambda dla informatyki bierze się
z analogii do struktury typów w typowanych językach programowania.
W rachunku tym każda zmienna ma przypisany typ, tzn. zakresem jej
zmienności jest zbiór opisywany przez ten typ. Typ wyrażenia
konstruuje się indukcyjnie z typów jego składowych. Term może być
stworzony poprzez aplikację funkcji do argumentu jedynie wtedy, gdy
typy funkcji i argumentu są zgodne, tzn. argument należy do
dziedziny funkcji.
Rachunek lambda z typami prostymi wprowadzony został przez Churcha
i Curry'ego. Jednakże istnieją istotne różnice pomiędzy
zaproponowanymi przez nich systemami typów. W systemie Curry'ego
termy typowanego rachunku lambda są po prostu wyróżnione spośród
termów beztypowego rachunku poprzez relację ustalającą typy. Tak
więc term
jest typowalny i należy do wyróżnionego zbioru termów, a term
typowalny nie jest i do zbioru nie należy. W systemie Churcha reguły nadające typy zostały wbudowane w reguły tworzenia termów. Nie będziemy omawiać żadnego z systemów konkretnie, a jedynie przedstawimy intuicje związane z systemem
Churcha nadawania typów.
Zakładamy, że mamy nieskończony, przeliczalny zbiór zmiennych (różnych od zmiennych ze zbioru
). Typ to wyrażenie zdefiniowane indukcyjnie:
- Każda zmienna jest typem, zwanym typem atomowym.
- Jeżeli
i
są typami, to
jest typem, zwanym typem złożonym.
Typy atomowe oznaczamy najczęściej
,
,
, a typy ogólnie
,
, ... Nawiasy są często pomijane zgodnie z zasadą łączności prawostronnej, tzn.
Fakt, że term
jest typu
(ma przypisany typ
), zapisujemy
jako
lub poprzez indeks dolny, na przykład
.
Typ, który nie może zostać przypisany żadnemu termowi, nazywamy typem pustym.
Termom nadajemy typy w następujący sposób:
- Jeżeli term
jest pojedynczą zmienną typu
, to
jest typu
, czyli
.
- Jeżeli term
, to
. Innymi słowy, jeżeli tworzymy program, który oczekuje na parametr typu
, a zwraca wynik typu
, to całość jest typu
.
- Jeżeli term
, gdzie
, a
, to
. Innymi słowy, jeżeli aplikujemy „funkcję”
typu
do argumentu
typu
, to wynik jest typu
.
Pytanie, czy term jest danego typu, jest problemem rozstrzygalnym,
tzn. istnieje algorytm, który dla zadanego termu
oraz typu
odpowiada, czy
jest typu
Rozstrzygalny jest także problem, czy danemu termowi przypisać można typ zgodnie z zasadami opisanymi powyżej.
Przykład
Parser nie mógł rozpoznać (SVG (MathML może zostać włączone przez wtyczkę w przeglądarce): Nieprawidłowa odpowiedź („Math extension cannot connect to Restbase.”) z serwera „https://wazniak.mimuw.edu.pl/api/rest_v1/”:): {\displaystyle \aligned \mbox{\bf Term:} & & \mbox{\bf Typ:}\\ \hline \lambda x : \alpha.x & & \alpha\to \alpha\\ \lambda x : \alpha\to \beta. x & & (\alpha\to \beta)\to (\alpha\to \beta)\\ \lambda x : \alpha. \lambda y : \beta. x & & \alpha\to (\beta\to \alpha)\\ \lambda x : \alpha\to \alpha. \lambda y : \alpha. x & & (\alpha\to \alpha)\to (\alpha\to (\alpha\to \alpha))\\ \lambda x : \alpha. \lambda y : \beta. y & & \alpha\to (\beta\to \beta)\\ \lambda x : \alpha\to \beta. \lambda y : \gamma\to \alpha. \lambda z : \gamma. x(yz) & & (\alpha\to \beta)\to((\gamma\to \alpha)\to (\gamma\to \beta))\\ \lambda x.xx & & \mbox{term nie jest typowalny} \\ \lambda xyz.xy(xz) & & \mbox{term nie jest typowalny} \\ \mbox{typ pusty} & & (\alpha\to \alpha) \to \beta\\ \mbox{typ pusty} & & ((\alpha\to \beta)\to \alpha)\to \alpha \end{align} }
Jeżeli
są typami, to zapis
oznacza typ
. Zatem każdy typ może być przedstawiony
w postaci
, gdzie
, a
jest typem atomowym.