Paradygmaty programowania/Wykład 8: U podstaw programowania funkcyjnego — rachunek lambda: Różnice pomiędzy wersjami
m (Zastępowanie tekstu - "\endaligned" na "\end{align}") |
m (Zastępowanie tekstu – „ </math>” na „</math>”) |
||
(Nie pokazano 11 pośrednich wersji utworzonych przez tego samego użytkownika) | |||
Linia 20: | Linia 20: | ||
* Dowolna zmienna ze zbioru <math>Var</math> jest termem rachunku lambda, czyli jeżeli <math>x\in Var</math>, to <math>x\in\Lambda</math>. | * Dowolna zmienna ze zbioru <math>Var</math> jest termem rachunku lambda, czyli jeżeli <math>x\in Var</math>, to <math>x\in\Lambda</math>. | ||
* Jeżeli weźmiemy dowolny term <math>M</math> ze zbioru <math>\Lambda</math>, oraz dowolną zmienną <math>x</math> ze zbioru <math>Var</math>, to term postaci <math>(\lambda x. M)</math> jest termem rachunku lambda, czyli jeżeli <math>M\in\Lambda</math> i <math>x\in Var | * Jeżeli weźmiemy dowolny term <math>M</math> ze zbioru <math>\Lambda</math>, oraz dowolną zmienną <math>x</math> ze zbioru <math>Var</math>, to term postaci <math>(\lambda x. M)</math> jest termem rachunku lambda, czyli jeżeli <math>M\in\Lambda</math> i <math>x\in Var</math>, to <math>(\lambda x.M)\in\Lambda</math>. Powyższy sposób tworzenia termu nazywamy ''abstrakcją''. | ||
* Inna metoda to metoda ''aplikacji'', czyli jeżeli termy <math>M\in\Lambda</math> i <math>N\in\Lambda | * Inna metoda to metoda ''aplikacji'', czyli jeżeli termy <math>M\in\Lambda</math> i <math>N\in\Lambda</math>, to <math>(MN)\in\Lambda</math>. | ||
Nawiasy są często pomijane według zasady łączności lewostronnej: na | Nawiasy są często pomijane według zasady łączności lewostronnej: na | ||
przykład <math>MNPQ \equiv (((MN)P)Q) | przykład <math>MNPQ \equiv (((MN)P)Q)</math>. Wieloargumentowe funkcje są | ||
reprezentowane przez funkcje jednoargumentowe, których argumentami | reprezentowane przez funkcje jednoargumentowe, których argumentami | ||
są funkcje. Zatem to, co zwykle jest zapisywane w postaci <math>F(N, M)</math>, | są funkcje. Zatem to, co zwykle jest zapisywane w postaci <math>F(N, M)</math>, | ||
w rachunku lambda zapisuje się <math>((FN)M)\equiv FNM | w rachunku lambda zapisuje się <math>((FN)M)\equiv FNM</math>. Powtórzenia | ||
symbolu <math>\lambda</math> pomijane są według zasady łączności prawostronnej, | symbolu <math>\lambda</math> pomijane są według zasady łączności prawostronnej, | ||
tzn. <math>\lambda xyz. M\equiv (\lambda x.(\lambda y.(\lambda z.M))) | tzn. <math>\lambda xyz. M\equiv (\lambda x.(\lambda y.(\lambda z.M)))</math>. | ||
W termie rachunku lambda zmienna może być wolna albo związana. ''Wolnym wystapieniem'' zmiennej <math>x</math> nazywamy tylko takie wystąpienie | W termie rachunku lambda zmienna może być wolna albo związana. ''Wolnym wystapieniem'' zmiennej <math>x</math> nazywamy tylko takie wystąpienie | ||
Linia 43: | Linia 43: | ||
* Jeżeli <math>M</math> jest postaci <math>\lambda x.M'</math>, to <math>\lambda x</math> wiąże wszystkie wolne wystąpienia zmiennej <math>x</math> w termie <math>M'</math>, a zatem <math>FV(M) = FV(M')-\{x\}</math>. | * Jeżeli <math>M</math> jest postaci <math>\lambda x.M'</math>, to <math>\lambda x</math> wiąże wszystkie wolne wystąpienia zmiennej <math>x</math> w termie <math>M'</math>, a zatem <math>FV(M) = FV(M')-\{x\}</math>. | ||
* Jeżeli <math>M</math> jest postaci <math>PQ</math>, to zmiennymi wolnymi termu <math>M</math> są wszystkie zmienne wolne występujące w termach <math>P</math> i <math>Q</math>, czyli <math>FV(M) = FV(P)\cup FV(Q) | * Jeżeli <math>M</math> jest postaci <math>PQ</math>, to zmiennymi wolnymi termu <math>M</math> są wszystkie zmienne wolne występujące w termach <math>P</math> i <math>Q</math>, czyli <math>FV(M) = FV(P)\cup FV(Q)</math>. | ||
Zmienna jest ''zmienną wolną'' termu <math>M</math>, jeżeli należy do | Zmienna jest ''zmienną wolną'' termu <math>M</math>, jeżeli należy do | ||
zbioru <math>FV(M) | zbioru <math>FV(M)</math>, | ||
a ''zmienną związaną'' w przeciwnym wypadku. | a ''zmienną związaną'' w przeciwnym wypadku. | ||
Term <math>M</math> jest ''domknięty'' wtedy i tylko wtedy, gdy <math>FV(M)=\emptyset | Term <math>M</math> jest ''domknięty'' wtedy i tylko wtedy, gdy <math>FV(M)=\emptyset</math>. | ||
'''Przykład''' | '''Przykład''' | ||
Linia 60: | Linia 60: | ||
<center><math>\lambda \underbrace{x_1 | <center><math>\lambda \underbrace{x_1\dots x_n}_{\mbox{parametry}}. | ||
\underbrace{M}_{\mbox{ | \underbrace{M}_{\mbox{ciało procedury}}</math></center> | ||
Linia 78: | Linia 78: | ||
<center><math>\lambda x. M\rightarrow_\alpha \lambda y. M[x/ y] | <center><math>\lambda x. M\rightarrow_\alpha \lambda y. M[x/ y]</math>,</center> | ||
Linia 92: | Linia 92: | ||
<center><math>\ | <center><math>\begin{align} | ||
\lambda x.x & =_\alpha \lambda y.y\\ | \lambda x.x & =_\alpha \lambda y.y\\ | ||
\lambda x.xz & =_\alpha \lambda y.yz\\ | \lambda x.xz & =_\alpha \lambda y.yz\\ | ||
Linia 123: | Linia 123: | ||
<center><math>(\lambda xy. xy)y =_\alpha (\lambda xz. xz)y =_\beta \lambda z. yz | <center><math>(\lambda xy. xy)y =_\alpha (\lambda xz. xz)y =_\beta \lambda z. yz</math>.</center> | ||
Linia 141: | Linia 141: | ||
Jeżeli term <math>M</math> poprzez pewne ciągi <math>\beta</math>-redukcji redukuje się do termów <math>N_1</math> i <math>N_2</math>, | Jeżeli term <math>M</math> poprzez pewne ciągi <math>\beta</math>-redukcji redukuje się do termów <math>N_1</math> i <math>N_2</math>, | ||
to istnieje term <math>M' </math> taki, że zarówno <math>N_1</math>, jak i <math>N_2</math> można zredukować do <math>M' </math> | to istnieje term <math>M'</math> taki, że zarówno <math>N_1</math>, jak i <math>N_2</math> można zredukować do <math>M'</math> | ||
(poprzez ciągi <math>\beta</math>-redukcji): | (poprzez ciągi <math>\beta</math>-redukcji): | ||
Linia 158: | Linia 158: | ||
<center><math>\Delta\Delta = (\lambda x.xx)\Delta =_\beta \Delta\Delta | <center><math>\Delta\Delta = (\lambda x.xx)\Delta =_\beta \Delta\Delta</math>.</center> | ||
Linia 166: | Linia 166: | ||
<center><math> | <center><math> | ||
\ | \begin{align} | ||
\overbrace{(\lambda x.y) (\underbrace{\Delta\Delta}_{(1)})}^{(2)} | \overbrace{(\lambda x.y) (\underbrace{\Delta\Delta}_{(1)})}^{(2)} | ||
& =_\beta (\lambda x.y)(\Delta\Delta) | & =_\beta (\lambda x.y)(\Delta\Delta) =_\beta & |\text{ redukując (1)}\\ | ||
& =_\beta y & |\text{ redukując (2)} | |||
\end{align} | \end{align} | ||
</math></center> | </math></center> | ||
Linia 196: | Linia 196: | ||
* Term <math>\lambda sx.\ x</math> reprezentuje liczbę naturalną 0. | * Term <math>\lambda sx.\ x</math> reprezentuje liczbę naturalną 0. | ||
* Term <math>\lambda sx.\ \underbrace{s( s( ... (s}_{\mbox{ | * Term <math>\lambda sx.\ \underbrace{s( s( ... (s}_{\mbox{ n razy}}x) ...))</math> reprezentuje liczbę naturalną <math>n</math>. | ||
Term <math>\lambda sx.\ x</math> to po prostu | Term <math>\lambda sx.\ x</math> to po prostu | ||
algorytm: „weź następnik <math>s</math>, weź zero <math>x</math> i zwróć zero”; natomiast term <math>\lambda sx.\ \underbrace{s( s( ... (s}_{\mbox{ | algorytm: „weź następnik <math>s</math>, weź zero <math>x</math> i zwróć zero”; natomiast term <math>\lambda sx.\ \underbrace{s( s( ... (s}_{\mbox{ n razy}}x) ...))</math> to algorytm: | ||
„weź następnik <math>s</math>, weź zero <math>x</math>, zastosuj <math>n</math>-krotnie następnik do zera i zwróć wynik”. | „weź następnik <math>s</math>, weź zero <math>x</math>, zastosuj <math>n</math>-krotnie następnik do zera i zwróć wynik”. | ||
Zatem liczba naturalna w naszym rozumieniu to procedura, która | Zatem liczba naturalna w naszym rozumieniu to procedura, która | ||
Linia 219: | Linia 219: | ||
Term | Term | ||
<center><math>\ | <center><math>\begin{align} | ||
A & = & \lambda mnsx. (ms)(nsx) | A & = & \lambda mnsx. (ms)(nsx) | ||
\end{align}</math></center> | \end{align}</math></center> | ||
Linia 234: | Linia 234: | ||
'''Przykład''' | '''Przykład''' | ||
Zobaczmy, jak działa term <math>A</math> na liczbach 1 i 2: | Zobaczmy, jak działa term <math>A</math> na liczbach 1 i 2: | ||
<center><math>\ | <center><math>\begin{align} | ||
A\underline{1}\ \underline{2} & = & & | A\underline{1}\ \underline{2} & = & & | ||
\{[\lambda m.\ (\lambda n.\ (\lambda sx.\ (ms)(nsx))]\underline{1}\} | \{[\lambda m.\ (\lambda n.\ (\lambda sx.\ (ms)(nsx))]\underline{1}\} | ||
\underline{2} & & | \underline{2} & & |\mbox{dopisując nawiasy i } \lambda\\ | ||
& =_\beta & &[\lambda n.\ (\lambda sx.\ (\underline{2}s)(nsx)]\underline{1} | & =_\beta & &[\lambda n.\ (\lambda sx.\ (\underline{2}s)(nsx)]\underline{1} | ||
& & | & & | m := 2\\ | ||
& =_\beta & & \lambda sx.\ (\underline{2} s)(\underline{1} sx) & & | & =_\beta & & \lambda sx.\ (\underline{2} s)(\underline{1} sx) & & | n := 1\\ | ||
& = & & \lambda sx.\ (\underline{2} s)\{[(\lambda t.\ (\lambda y.\ ty))s]x\} | & = & & \lambda sx.\ (\underline{2} s)\{[(\lambda t.\ (\lambda y.\ ty))s]x\} | ||
& & \mbox{ | & & |\mbox{ dopisując nawiasy i } \lambda\\ | ||
& =_\beta & & \lambda sx.\ (\underline{2} s)[(\lambda y.\ sy)x] & & | & =_\beta & & \lambda sx.\ (\underline{2} s)[(\lambda y.\ sy)x] & & | t := s\\ | ||
& =_\beta & & \lambda sx.\ (\underline{2} s)(sx) & & | & =_\beta & & \lambda sx.\ (\underline{2} s)(sx) & & | y := x\\ | ||
& = & & \lambda sx.\ \{\underbrace{[ | & = & & \lambda sx.\ \{\underbrace{[ | ||
\lambda t.\ (\lambda y. t(ty))]}_{\mbox{ | \lambda t.\ (\lambda y. t(ty))]}_{\mbox{ procedura licząca 2}} | ||
s\} \underbrace{(sx)}_{\mbox{ | s\} \underbrace{(sx)}_{\mbox{ „zero”}} & & | ||
\mbox{ | |\mbox{ dopisując nawiasy i } \lambda\\ | ||
& =_\beta & & \lambda sx.\ (\lambda y.\ s(sy))(sx) & & | & =_\beta & & \lambda sx.\ (\lambda y.\ s(sy))(sx) & & | t := s\\ | ||
& =_\beta & & \lambda sx.\ s(s(sx)) & & | & =_\beta & & \lambda sx.\ s(s(sx)) & & | y := sx\\ | ||
& = & & \underline{3} | & = & & \underline{3} | ||
\end{align}</math></center> | \end{align}</math></center> | ||
Linia 257: | Linia 256: | ||
Term | Term | ||
<center><math>\ | <center><math>\begin{align} | ||
M & = & \lambda mnsx. n(ms)x | M & = & \lambda mnsx. n(ms)x | ||
\end{align}</math></center> | \end{align}</math></center> | ||
Linia 268: | Linia 267: | ||
'''Przykład''' | '''Przykład''' | ||
<center><math>\ | <center><math>\begin{align} | ||
M\underline{2}\ \underline{2} & = & & | M\underline{2}\ \underline{2} & = & & | ||
\{[\lambda m.\ (\lambda n.\ (\lambda sx.\ n(ms)x))]\underline{2}\} | \{[\lambda m.\ (\lambda n.\ (\lambda sx.\ n(ms)x))]\underline{2}\} | ||
\underline{2} & & | \underline{2} & & | ||
\mbox{ | |\mbox{dopisując nawiasy i } \lambda\\ | ||
& =_\beta & & [\lambda n.\ (\lambda sx.\ n(\underline{2}s)x)]\underline{2} | & =_\beta & & [\lambda n.\ (\lambda sx.\ n(\underline{2}s)x)]\underline{2} | ||
& & | & & | m := 2\\ | ||
& =_\beta & & \lambda sx.\ \underline{2} (\underline{2} s)x & & | & =_\beta & & \lambda sx.\ \underline{2} (\underline{2} s)x & & | n := 2\\ | ||
& = & & \lambda sx.\ \underline{2} [\underbrace{(\lambda t.\ | & = & & \lambda sx.\ \underline{2} [\underbrace{(\lambda t.\ | ||
(\lambda y.\ t(ty)))}_{\mbox{ | (\lambda y.\ t(ty)))}_{\mbox{ procedura licząca 2}}s]x | ||
& & \mbox{ | & & |\mbox{ dopisując nawiasy i } \lambda\\ | ||
& =_\beta & & \lambda sx.\ \underline{2} [\lambda y.\ s(sy)] x & & | & =_\beta & & \lambda sx.\ \underline{2} [\lambda y.\ s(sy)] x & & | t := s\\ | ||
& = & & \lambda sx.\ \underbrace{[ | & = & & \lambda sx.\ \underbrace{[ | ||
\lambda t.\ (\lambda z. t(tz))]}_{\mbox{ | \lambda t.\ (\lambda z. t(tz))]}_{\mbox{ procedura licząca 2}} | ||
\underbrace{[\lambda y. s(sy)]}_{\mbox{ | \underbrace{[\lambda y. s(sy)]}_{\mbox{ dodawanie 2}} x & & | ||
\mbox{ | |\mbox{ dopisując nawiasy i } \lambda\\ | ||
& =_\beta & & \lambda sx.\ (\lambda z.\ [\lambda y. s(sy)] | & =_\beta & & \lambda sx.\ (\lambda z.\ [\lambda y. s(sy)] | ||
([\lambda y. s(sy)]z))x & & | ([\lambda y. s(sy)]z))x & & | t := [\lambda y. s(sy)]\\ | ||
& =_\beta & & \lambda sx.\ [\lambda y. s(sy)] ([\lambda y. s(sy)]z) & & | & =_\beta & & \lambda sx.\ [\lambda y. s(sy)] ([\lambda y. s(sy)]z) & & | z := x\\ | ||
& =_\beta & & \lambda sx.\ s(s[s(sx)]) & & | & =_\beta & & \lambda sx.\ s(s[s(sx)]) & & | y := s(sx)\\ | ||
& = & & \underline{4} | & = & & \underline{4} | ||
\end{align}</math></center> | \end{align}</math></center> | ||
Linia 293: | Linia 292: | ||
Term | Term | ||
<center><math>\ | <center><math>\begin{align} | ||
C & = & \lambda mnksx. m(\lambda y.nsx)(ksx) | C & = & \lambda mnksx. m(\lambda y.nsx)(ksx) | ||
\end{align}</math></center> | \end{align}</math></center> | ||
Linia 308: | Linia 307: | ||
Przykład pierwszy | Przykład pierwszy | ||
<center><math>\ | <center><math>\begin{align} | ||
C \underline{0}\ \underline{n}\ \underline{k} & =_\beta & & | |||
\lambda sx.\ \underline{0} (\lambda y.\ \underline{n} sx) | \lambda sx.\ \underline{0} (\lambda y.\ \underline{n} sx) | ||
(\underline{k} sx)\\ | (\underline{k} sx)\\ | ||
Linia 319: | Linia 318: | ||
Przykład drugi | Przykład drugi | ||
<center><math>\ | <center><math>\begin{align} | ||
C \underline{m}\ \underline{n}\ \underline{k} & =_\beta & & | C \underline{m}\ \underline{n}\ \underline{k} & =_\beta & & | ||
\lambda sx. \underline{m} (\lambda y. \underline{n} sx) | \lambda sx. \underline{m} (\lambda y. \underline{n} sx) | ||
(\underline{k} sx) \\ | (\underline{k} sx) \\ | ||
& = & & \lambda sx.\ [\lambda tz.\ \underbrace{t(... (t}_{ | & = & & \lambda sx.\ [\lambda tz.\ \underbrace{t(... (t}_{ | ||
m\neq 0 \mbox{ razy}}y) ...)] | m\neq 0 \mbox{ razy}}y) ...)] | ||
(\lambda y.\ \underline{n} sx)(\underline{k} sx) \\ | (\lambda y.\ \underline{n} sx)(\underline{k} sx) \\ | ||
Linia 339: | Linia 338: | ||
<center><math>\ | <center><math>\begin{align} | ||
E & = & \lambda mn. (mn) | E & = & \lambda mn. (mn) | ||
\end{align}</math></center> | \end{align}</math></center> | ||
Linia 350: | Linia 349: | ||
<center><math> | <center><math> | ||
\ | \begin{align} | ||
E\underline{2}\ \underline{2} & = & & [\lambda mn.\ (mn)] | E\underline{2}\ \underline{2} & = & & [\lambda mn.\ (mn)] | ||
\underline{2}\ \underline{2}\\ | \underline{2}\ \underline{2}\\ | ||
& =_\beta & & \underline{2}\ \underline{2} | & =_\beta & & \underline{2}\ \underline{2} | ||
& & | & & | m := \underline{2},\ n := \underline{2}\\ | ||
& = & & \underbrace{[\lambda sx.\ s(sx)]}_{\underline{2}}\underline{2} &\\ | & = & & \underbrace{[\lambda sx.\ s(sx)]}_{\underline{2}}\underline{2} &\\ | ||
& =_\beta & & \lambda x.\ \underline{2} (\underline{2} x) | & =_\beta & & \lambda x.\ \underline{2} (\underline{2} x) | ||
& & | & & | s := \underline{2}\\ | ||
& =_\alpha & & \lambda s.\ \underline{2} (\underline{2} s) & & | & =_\alpha & & \lambda s.\ \underline{2} (\underline{2} s) & & | x := s\\ | ||
& = & & \lambda s. [\lambda tx.\ t(tx)](\underline{2} s)\\ | & = & & \lambda s. [\lambda tx.\ t(tx)](\underline{2} s)\\ | ||
& =_\beta & & \lambda sx.\ (\underline{2} s)((\underline{2} s)x) | & =_\beta & & \lambda sx.\ (\underline{2} s)((\underline{2} s)x) | ||
& & | & & | t := \underline{2} s\\ | ||
& = & & \lambda sx.\ (\underbrace{[\lambda ty. t(ty)]}_{\underline{2}}s) | & = & & \lambda sx.\ (\underbrace{[\lambda ty. t(ty)]}_{\underline{2}}s) | ||
((\underbrace{[\lambda pz. p(pz)]}_{\underline{2}}s)x)\\ | ((\underbrace{[\lambda pz. p(pz)]}_{\underline{2}}s)x)\\ | ||
& =_\beta & & \lambda sx.\ [\lambda y.\ s(sy)](s(sx)) | & =_\beta & & \lambda sx.\ [\lambda y.\ s(sy)](s(sx)) | ||
& & | & & | t := s,\ p := s,\ z := x\\ | ||
& =_\beta & & \lambda sx.\ s(s[s(sx)]) | & =_\beta & & \lambda sx.\ s(s[s(sx)]) | ||
& & | & & | y := s(sx)\\ | ||
& = & & \underline{4} | & = & & \underline{4} | ||
\end{align} | \end{align} | ||
Linia 376: | Linia 375: | ||
<center><math>\ | <center><math>\begin{align} | ||
P & = & \lambda n. (n (\lambda z. <\!\! A\underline{1} (z\underline{0}), | P & = & \lambda n. (n (\lambda z. <\!\! A\underline{1} (z\underline{0}), | ||
(z\underline{0})\!\!>) <\!\!\underline{0}, \underline{0}\!\!>) | (z\underline{0})\!\!>) <\!\!\underline{0}, \underline{0}\!\!>) | ||
Linia 388: | Linia 387: | ||
<center><math> | <center><math> | ||
\ | \begin{align} | ||
<0, 0> & \ | <0, 0> & \ \\ | ||
<1, 0> & \mbox{ | <1, 0> & \ \ \ \ \mbox{ krok 1}\\ | ||
<2, 1> & \mbox{ | <2, 1> & \ \ \ \ \mbox{ krok 2}\\ | ||
... \\ | ... \\ | ||
<n, n-1> & \mbox{ | <n, n-1> & \ \ \ \ \mbox{ krok } n | ||
\end{align} | \end{align} | ||
</math></center> | </math></center> | ||
Linia 406: | Linia 405: | ||
<center><math> | <center><math> | ||
\ | \begin{align} | ||
P\underline{2} & = & & \{\lambda n. (n (\lambda z. <\!\!A\underline{1} | P\underline{2} & = & & \{\lambda n. (n (\lambda z. <\!\!A\underline{1} | ||
(z\underline{0}), (z\underline{0}) \!\!>) <\!\!\underline{0}, | (z\underline{0}), (z\underline{0}) \!\!>) <\!\!\underline{0}, | ||
Linia 413: | Linia 412: | ||
(z\underline{0}), (z\underline{0}) \!\!> ) | (z\underline{0}), (z\underline{0}) \!\!> ) | ||
<\!\!\underline{0}, \underline{0}\!\!> )\underline{1}\\ | <\!\!\underline{0}, \underline{0}\!\!> )\underline{1}\\ | ||
& = & & ((\underbrace{\lambda sx.\ s(sx)}_{\mbox{ | & = & & ((\underbrace{\lambda sx.\ s(sx)}_{\mbox{ procedura licząca 2}}) | ||
(\underbrace{\lambda z. <\!\!A\underline{1}(z\underline{0}), | (\underbrace{\lambda z. <\!\!A\underline{1}(z\underline{0}), | ||
(z\underline{0}) \!\!>}_{\mbox{ | (z\underline{0}) \!\!>}_{\mbox{ następnik}}) | ||
\underbrace{<\!\!\underline{0}, \underline{0}\!\!>}_{\mbox{ | \underbrace{<\!\!\underline{0}, \underline{0}\!\!>}_{\mbox{ zero}}) | ||
\underline{1}\\ | \underline{1}\\ | ||
& =_\beta & & ((\lambda z. <\!\! A\underline{1} | & =_\beta & & ((\lambda z. <\!\! A\underline{1} | ||
Linia 449: | Linia 448: | ||
<center><math>{\cal Y}M =_\beta M({\cal Y}M) | <center><math>{\cal Y}M =_\beta M({\cal Y}M)</math>.</center> | ||
Linia 462: | Linia 461: | ||
<center><math>\ | <center><math>\begin{align} | ||
{\cal Y} M & = & &( \lambda y. (\lambda x. y(xx))(\lambda x. y(xx)))M | {\cal Y} M & = & &( \lambda y. (\lambda x. y(xx))(\lambda x. y(xx)))M | ||
& & \mbox{ | & & | \mbox{ z definicji } {\cal Y} \\ | ||
& =_\beta & & (\lambda x. M(xx))\underbrace{(\lambda x. M(xx))}_B | & =_\beta & & (\lambda x. M(xx))\underbrace{(\lambda x. M(xx))}_B | ||
& & | & & | y := M\\ | ||
& =_\beta & & M(\underbrace{(\lambda x. M(xx))}_B \underbrace{(\lambda x. M(xx))}_B ) | & =_\beta & & M(\underbrace{(\lambda x. M(xx))}_B \underbrace{(\lambda x. M(xx))}_B ) | ||
& & | & & | x := B\\ | ||
& =_\beta & & M( {\cal Y} M ) | & =_\beta & & M( {\cal Y} M ) | ||
& & | & & | {\cal Y} M = (\lambda x. M(xx))(\lambda x. M(xx))\\ | ||
\end{align} | \end{align} | ||
</math></center> | </math></center> | ||
Linia 501: | Linia 500: | ||
Typy atomowe oznaczamy najczęściej <math>\alpha</math>, <math>\beta</math>, <math>\gamma</math>, a typy ogólnie <math>\sigma</math>, <math>\tau</math>, ... Nawiasy są często pomijane zgodnie z zasadą łączności prawostronnej, tzn. | Typy atomowe oznaczamy najczęściej <math>\alpha</math>, <math>\beta</math>, <math>\gamma</math>, a typy ogólnie <math>\sigma</math>, <math>\tau</math>, ... Nawiasy są często pomijane zgodnie z zasadą łączności prawostronnej, tzn. | ||
<center><math>\gamma\to \sigma\to \tau \equiv (\gamma\to (\sigma\to \tau)) | <center><math>\gamma\to \sigma\to \tau \equiv (\gamma\to (\sigma\to \tau))</math>.</center> | ||
Linia 519: | Linia 518: | ||
Pytanie, czy term jest danego typu, jest problemem rozstrzygalnym, | Pytanie, czy term jest danego typu, jest problemem rozstrzygalnym, | ||
tzn. istnieje algorytm, który dla zadanego termu <math>M</math> oraz typu | tzn. istnieje algorytm, który dla zadanego termu <math>M</math> oraz typu | ||
<math>\tau</math> odpowiada, czy <math>M</math> jest typu <math>\tau | <math>\tau</math> odpowiada, czy <math>M</math> jest typu <math>\tau</math>. Rozstrzygalny jest także problem, czy danemu termowi przypisać można typ zgodnie z zasadami opisanymi powyżej. | ||
'''Przykład''' | '''Przykład''' | ||
Linia 525: | Linia 524: | ||
<center><math> | <center><math> | ||
\ | \begin{align} | ||
\ | \mathbf{ Term:} & & \mathbf{Typ:}\\ | ||
\hline | \hline | ||
\lambda x : \alpha.x & & \alpha\to \alpha\\ | \lambda x : \alpha.x & & \alpha\to \alpha\\ | ||
Linia 544: | Linia 543: | ||
Jeżeli <math>\tau_1,\ldots, \tau_n | Jeżeli <math>\tau_1,\ldots, \tau_n</math>, <math>\tau</math> są typami, to zapis | ||
<math>\tau_1, ..., \tau_n\to \tau</math> oznacza typ <math>\tau_1\to (\tau_2\to | <math>\tau_1, ..., \tau_n\to \tau</math> oznacza typ <math>\tau_1\to (\tau_2\to | ||
... \to (\tau_n\to \tau)...)</math>. Zatem każdy typ może być przedstawiony | ... \to (\tau_n\to \tau)...)</math>. Zatem każdy typ może być przedstawiony | ||
w postaci <math>\tau_1, ..., \tau_n \to \alpha</math>, gdzie <math>n\ge 0</math>, a <math>\alpha</math> | w postaci <math>\tau_1, ..., \tau_n \to \alpha</math>, gdzie <math>n\ge 0</math>, a <math>\alpha</math> | ||
jest typem atomowym. | jest typem atomowym. |
Aktualna wersja na dzień 10:00, 5 wrz 2023
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ę:
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
-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:
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 reprezentuje liczbę naturalną .
Term to po prostu algorytm: „weź następnik , weź zero i zwróć zero”; natomiast term 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
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:
Term
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
Term
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
Przykład drugi
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
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:
Ciekawym termem jest też term reprezentujący poprzednik:
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:
Zatem term definiuje funkcję , gdzie .
Przykład Zobaczmy teraz, jak działa term opisujący powyższą procedurę. Policzmy:
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 :
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
Jeżeli , są typami, to zapis
oznacza typ . Zatem każdy typ może być przedstawiony
w postaci , gdzie , a
jest typem atomowym.