Paradygmaty programowania/Wykład 8: U podstaw programowania funkcyjnego — rachunek lambda: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
m (Zastępowanie tekstu - "\endaligned" na "\end{align}")
Linia 96: Linia 96:
\lambda x.xz & =_\alpha \lambda y.yz\\
\lambda x.xz & =_\alpha \lambda y.yz\\
\lambda x.xy & \neq_{\alpha} \lambda x.xz.
\lambda x.xy & \neq_{\alpha} \lambda x.xz.
\endaligned</math></center>
\end{align}</math></center>




Linia 170: Linia 170:
& =_\beta (\lambda x.y)(\Delta\Delta) & =_\beta & \mbox{~~~~...} & \mbox{~~~~~~| redukując (1)}\\
& =_\beta (\lambda x.y)(\Delta\Delta) & =_\beta & \mbox{~~~~...} & \mbox{~~~~~~| redukując (1)}\\
  & =_\beta y & & & \mbox{~~~~~~| redukując (2)}
  & =_\beta y & & & \mbox{~~~~~~| redukując (2)}
\endaligned
\end{align}
</math></center>
</math></center>


Linia 221: Linia 221:
<center><math>\aligned
<center><math>\aligned
A & = & \lambda mnsx. (ms)(nsx)
A & = & \lambda mnsx. (ms)(nsx)
\endaligned</math></center>
\end{align}</math></center>


reprezentuje dodawanie.
reprezentuje dodawanie.
Linia 253: Linia 253:
   & =_\beta & & \lambda sx.\ s(s(sx)) & & \mbox{~~~~~~| } y := sx\\
   & =_\beta & & \lambda sx.\ s(s(sx)) & & \mbox{~~~~~~| } y := sx\\
   & = & & \underline{3}
   & = & & \underline{3}
\endaligned</math></center>
\end{align}</math></center>


Term
Term
Linia 259: Linia 259:
<center><math>\aligned
<center><math>\aligned
M & = & \lambda mnsx. n(ms)x
M & = & \lambda mnsx. n(ms)x
\endaligned</math></center>
\end{align}</math></center>


reprezentuje mnożenie. Procedura mnożenia oczekuje na podanie dwóch argumentów <math>m</math>  
reprezentuje mnożenie. Procedura mnożenia oczekuje na podanie dwóch argumentów <math>m</math>  
Linia 289: Linia 289:
   & =_\beta & & \lambda sx.\ s(s[s(sx)]) & &\mbox{~~~~~~| } y := s(sx)\\
   & =_\beta & & \lambda sx.\ s(s[s(sx)]) & &\mbox{~~~~~~| } y := s(sx)\\
   & = & & \underline{4}
   & = & & \underline{4}
\endaligned</math></center>
\end{align}</math></center>


Term
Term
Linia 295: Linia 295:
<center><math>\aligned
<center><math>\aligned
C & = & \lambda mnksx. m(\lambda y.nsx)(ksx)
C & = & \lambda mnksx. m(\lambda y.nsx)(ksx)
\endaligned</math></center>
\end{align}</math></center>


reprezentuje funkcję
reprezentuje funkcję
Linia 316: Linia 316:
   & =_\beta & & \lambda sx.\ \underline{k} sx \\
   & =_\beta & & \lambda sx.\ \underline{k} sx \\
   & =_\beta & & \underline{k}
   & =_\beta & & \underline{k}
\endaligned</math></center>
\end{align}</math></center>


Przykład drugi
Przykład drugi
Linia 329: Linia 329:
     (... ([\lambda y.\ \underline{n} sx](\underline{k} sx)) ...)\\
     (... ([\lambda y.\ \underline{n} sx](\underline{k} sx)) ...)\\
   & =_\beta & & \lambda sx. \underline{n} sx
   & =_\beta & & \lambda sx. \underline{n} sx
\endaligned</math></center>
\end{align}</math></center>


===Przekazanie funkcji jako parametru===
===Przekazanie funkcji jako parametru===
Linia 341: Linia 341:
<center><math>\aligned
<center><math>\aligned
E & = & \lambda mn. (mn)
E & = & \lambda mn. (mn)
\endaligned</math></center>
\end{align}</math></center>




Linia 369: Linia 369:
& & \mbox{~~~~~~| } y := s(sx)\\
& & \mbox{~~~~~~| } y := s(sx)\\
& = & & \underline{4}
& = & & \underline{4}
\endaligned
\end{align}
</math></center>
</math></center>


Linia 380: Linia 380:
(z\underline{0})\!\!>) <\!\!\underline{0}, \underline{0}\!\!>)
(z\underline{0})\!\!>) <\!\!\underline{0}, \underline{0}\!\!>)
\underline{1},
\underline{1},
\endaligned</math></center>
\end{align}</math></center>




Linia 394: Linia 394:
... \\
... \\
<n, n-1> & \mbox{~~~~~~krok } n
<n, n-1> & \mbox{~~~~~~krok } n
\endaligned
\end{align}
</math></center>
</math></center>


Linia 434: Linia 434:
\underline{1}\\
\underline{1}\\
& =_\beta & & \underline{1}
& =_\beta & & \underline{1}
\endaligned
\end{align}
</math></center>
</math></center>


Linia 471: Linia 471:
& =_\beta & & M( {\cal Y} M )  
& =_\beta & & M( {\cal Y} M )  
& & \mbox{~~~~~~| } {\cal Y} M = (\lambda x. M(xx))(\lambda x. M(xx))\\
& & \mbox{~~~~~~| } {\cal Y} M = (\lambda x. M(xx))(\lambda x. M(xx))\\
\endaligned
\end{align}
</math></center>
</math></center>


Linia 540: Linia 540:
\mbox{typ pusty} & & (\alpha\to \alpha) \to \beta\\
\mbox{typ pusty} & & (\alpha\to \alpha) \to \beta\\
\mbox{typ pusty} & & ((\alpha\to \beta)\to \alpha)\to \alpha
\mbox{typ pusty} & & ((\alpha\to \beta)\to \alpha)\to \alpha
\endaligned
\end{align}
</math></center>
</math></center>



Wersja z 12:31, 9 cze 2020


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 , czyli

Zmienna jest zmienną wolną termu , jeżeli należy do zbioru zmienną związaną w przeciwnym wypadku.

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 M} jest domknięty wtedy i tylko wtedy, gdy 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 FV(M)=\emptyset.}

Przykład

  • W termie 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 M = y(\lambda x.x)} zmienna 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 y} jest zmienną wolną, a 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 x} 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ć (błąd składni): {\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 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ć (nieznana funkcja „\aligned”): {\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 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 , to istnieje term taki, że zarówno , jak i  można zredukować do (poprzez ciągi -redukcji):


Paradygmaty programowania/Moduł8


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ć (nieznana funkcja „\aligned”): {\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ć (błąd składni): {\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ć (błąd składni): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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ć (nieznana funkcja „\aligned”): {\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 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ć (nieznana funkcja „\aligned”): {\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.