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 – „ </math>” na „</math>”)
 
(Nie pokazano 22 wersji utworzonych przez 2 użytkowników)
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,</math> to <math>(\lambda x.M)\in\Lambda</math>. Powyższy sposób tworzenia termu nazywamy ''abstrakcją''.
* 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,</math> to <math>(MN)\in\Lambda</math>.
* 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).</math> Wieloargumentowe funkcje są
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.</math> Powtórzenia
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))).</math>
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&nbsp;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&nbsp;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&nbsp;<math>Q</math>, czyli <math>FV(M) = FV(P)\cup FV(Q).</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&nbsp;<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),</math>
zbioru <math>FV(M)</math>,
a&nbsp;''zmienną związaną'' w&nbsp;przeciwnym wypadku.
a&nbsp;''zmienną związaną'' w&nbsp;przeciwnym wypadku.


Term <math>M</math> jest ''domknięty'' wtedy i tylko wtedy, gdy <math>FV(M)=\emptyset.</math>
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... x_n}_{\mbox{\tiny parametry}}.
<center><math>\lambda \underbrace{x_1\dots x_n}_{\mbox{parametry}}.
\underbrace{M}_{\mbox{\tiny ciało procedury}}</math></center>
\underbrace{M}_{\mbox{ciało procedury}}</math></center>




Linia 78: Linia 78:




<center><math>\lambda x. M\rightarrow_\alpha \lambda y. M[x/ y],</math></center>
<center><math>\lambda x. M\rightarrow_\alpha \lambda y. M[x/ y]</math>,</center>




Linia 92: Linia 92:




<center><math>\aligned
<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\\
\lambda x.xy & \neq_{\alpha} & \lambda x.xz.
\lambda x.xy & \neq_{\alpha} \lambda x.xz.
\endaligned</math></center>
\end{align}</math></center>




Linia 123: Linia 123:




<center><math>(\lambda xy. xy)y =_\alpha (\lambda xz. xz)y =_\beta \lambda z. yz.</math></center>
<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&nbsp;<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&nbsp;<math>N_2</math>,
to istnieje term <math>M' </math> taki, że zarówno <math>N_1</math>, jak i&nbsp;<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&nbsp;<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.</math></center>
<center><math>\Delta\Delta = (\lambda x.xx)\Delta =_\beta \Delta\Delta</math>.</center>




Linia 166: Linia 166:


<center><math>
<center><math>
\aligned
\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 & ... & \mbox{redukując (1)}\\
& =_\beta (\lambda x.y)(\Delta\Delta) =_\beta & |\text{ redukując (1)}\\
& =_\beta & y & & & \mbox{redukując (2)}
& =_\beta y & |\text{ redukując (2)}
\endaligned
\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{\tiny n razy}}x) ...))</math> reprezentuje liczbę naturalną <math>n</math>.
* 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&nbsp;zwróć zero”; natomiast term <math>\lambda sx.\ \underbrace{s( s( ... (s}_{\mbox{\tiny n razy}}x) ...))</math> to algorytm:
algorytm: „weź następnik <math>s</math>, weź zero <math>x</math> i&nbsp;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&nbsp;zwróć wynik”.
„weź następnik <math>s</math>, weź zero <math>x</math>, zastosuj <math>n</math>-krotnie następnik do zera i&nbsp;zwróć wynik”.
Zatem liczba naturalna w&nbsp;naszym rozumieniu to procedura, która
Zatem liczba naturalna w&nbsp;naszym rozumieniu to procedura, która
Linia 219: Linia 219:
Term
Term


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


reprezentuje dodawanie.
reprezentuje dodawanie.
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>\aligned
<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\\
    \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\\
     & \mbox{~~~~~~| } m := 2\\
   & =_\beta & & \lambda sx.\ (\underline{2} s)(\underline{1} sx) & & | n := 1\\
   & =_\beta & \lambda sx.\ (\underline{2} s)(\underline{1} sx) & \mbox{~~~~~~| } 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{ dopisując nawiasy i } \lambda\\
   &   \mbox{~~~~~~| dopisując nawiasy i } \lambda\\
   & =_\beta & & \lambda sx.\ (\underline{2} s)[(\lambda y.\ sy)x] & | t := s\\
   & =_\beta & \lambda sx.\ (\underline{2} s)[(\lambda y.\ sy)x] & \mbox{~~~~~~| } t := s\\
   & =_\beta & & \lambda sx.\ (\underline{2} s)(sx) & & | y := x\\
   & =_\beta & \lambda sx.\ (\underline{2} s)(sx) & \mbox{~~~~~~| } y := x\\
   & = & & \lambda sx.\ \{\underbrace{[
   & = & \lambda sx.\ \{\underbrace{[
     \lambda t.\ (\lambda y. t(ty))]}_{\mbox{ procedura licząca 2}}
     \lambda t.\ (\lambda y. t(ty))]}_{\mbox{\tiny procedura licząca 2}}
     s\} \underbrace{(sx)}_{\mbox{ „zero”}} & &
     s\}
     |\mbox{ dopisując nawiasy i } \lambda\\
    \underbrace{(sx)}_{\mbox{\tiny „zero”}} &
   & =_\beta & & \lambda sx.\ (\lambda y.\ s(sy))(sx) & | t := s\\
     \mbox{~~~~~~| dopisując nawiasy i } \lambda\\
   & =_\beta & & \lambda sx.\ s(s(sx)) & & | y := sx\\
   & =_\beta & \lambda sx.\ (\lambda y.\ s(sy))(sx) & \mbox{~~~~~~| } t := s\\
   & = & & \underline{3}
   & =_\beta & \lambda sx.\ s(s(sx)) & \mbox{~~~~~~| } y := sx\\
\end{align}</math></center>
   & = & \underline{3}
\endaligned</math></center>


Term
Term


<center><math>\aligned
<center><math>\begin{align}
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 269: Linia 267:


'''Przykład'''
'''Przykład'''
<center><math>\aligned
<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{~~~~~~| dopisując nawiasy i } \lambda\\
     |\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}
     & \mbox{~~~~~~| } m := 2\\
     & & | m := 2\\
   & =_\beta & \lambda sx.\ \underline{2} (\underline{2} s)x & \mbox{~~~~~~| } n := 2\\
   & =_\beta & & \lambda sx.\ \underline{2} (\underline{2} s)x & & | n := 2\\
   & = & \lambda sx.\ \underline{2} [
   & = & & \lambda sx.\ \underline{2} [\underbrace{(\lambda t.\  
    \underbrace{(\lambda t.\ (\lambda y.\ t(ty)))}_{\mbox{\tiny
      (\lambda y.\ t(ty)))}_{\mbox{ procedura licząca 2}}s]x
      procedura licząca 2}}s]x
   & & |\mbox{ dopisując nawiasy i } \lambda\\
   &   \mbox{~~~~~~| dopisując nawiasy i } \lambda\\
   & =_\beta & & \lambda sx.\ \underline{2} [\lambda y.\ s(sy)] x & & | t := s\\
   & =_\beta & \lambda sx.\ \underline{2} [\lambda y.\ s(sy)] x & \mbox{~~~~~~| } t := s\\
   & = & & \lambda sx.\ \underbrace{[
   & = & \lambda sx.\ \underbrace{[
     \lambda t.\ (\lambda z. t(tz))]}_{\mbox{ procedura licząca 2}}
     \lambda t.\ (\lambda z. t(tz))]}_{\mbox{\tiny procedura licząca 2}}
     \underbrace{[\lambda y. s(sy)]}_{\mbox{ dodawanie 2}} x & &
     \underbrace{[\lambda y. s(sy)]}_{\mbox{\tiny dodawanie 2}} x &
     |\mbox{ dopisując nawiasy i } \lambda\\
     \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 & & | t := [\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) & & | z := x\\
   & =_\beta & \lambda sx.\ [\lambda y. s(sy)] ([\lambda y. s(sy)]z) & \mbox{~~~~~~| } z := x\\
   & =_\beta & & \lambda sx.\ s(s[s(sx)]) & & | y := s(sx)\\
   & =_\beta & \lambda sx.\ s(s[s(sx)]) & \mbox{~~~~~~| } y := s(sx)\\
   & = & & \underline{4}
   & = & \underline{4}
\end{align}</math></center>
\endaligned</math></center>


Term
Term


<center><math>\aligned
<center><math>\begin{align}
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 303: Linia 300:
i tak jak w poprzednich przykładach zwraca procedurę obliczającą
i tak jak w poprzednich przykładach zwraca procedurę obliczającą
liczbę naturalną. W przypadku, gdy pierwszy parametr <math>m</math> jest
liczbę naturalną. W przypadku, gdy pierwszy parametr <math>m</math> jest
zerem, procedura wynikowa liczy liczbę <math>k</math>, w przeciwnym przypadku
zerem, procedura wynikowa liczy liczbę <math>k</math>, w przeciwnym przypadku liczbę <math>n</math>.
-- liczbę <math>n</math>.


'''Przykład'''
'''Przykład'''
Linia 311: Linia 307:


Przykład pierwszy
Przykład pierwszy
<center><math>\aligned
<center><math>\begin{align}
   \mbox{} C \underline{0}\ \underline{n}\ \underline{k} & =_\beta &
   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)\\
   & = & \lambda sx.\ \underbrace{(\lambda tz. z)}_{\underline{0}}
   & = & & \lambda sx.\ \underbrace{(\lambda tz. z)}_{\underline{0}}
     (\lambda y.\ \underline{n} sx)(\underline{k}sx) \\
     (\lambda y.\ \underline{n} sx)(\underline{k}sx) \\
   & =_\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
<center><math>\aligned
<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}_{\tiny
   & = & & \lambda sx.\ [\lambda tz.\ \underbrace{t(... (t}_{
     m\neq 0 razy}y) ...)]
     m\neq 0 \mbox{ razy}}y) ...)]
     (\lambda y.\ \underline{n} sx)(\underline{k} sx) \\
     (\lambda y.\ \underline{n} sx)(\underline{k} sx) \\
   & =_\beta & \lambda sx. [\lambda y.\ \underline{n} sx]
   & =_\beta & & \lambda sx. [\lambda y.\ \underline{n} sx]
     (... ([\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 342: Linia 338:




<center><math>\aligned
<center><math>\begin{align}
E & = & \lambda mn. (mn)
E & = & \lambda mn. (mn)
\endaligned</math></center>
\end{align}</math></center>




Linia 353: Linia 349:


<center><math>
<center><math>
\aligned
\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} & \mbox{~~~~~~| } m := \underline{2},\
& =_\beta & & \underline{2}\ \underline{2}  
n := \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)  
\mbox{~~~~~~| } s := \underline{2}\\
& & | s := \underline{2}\\
& =_\alpha & \lambda s.\ \underline{2} (\underline{2} s) & \mbox{~~~~~~| } x := 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)
& \mbox{~~~~~~| } t := \underline{2} s\\
& | 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))
& \mbox{~~~~~~| } t := s,\ p := s,\ z := x\\
& & | t := s,\ p := s,\ z := x\\
& =_\beta & \lambda sx.\ s(s[s(sx)]) & \mbox{~~~~~~| } y := s(sx)\\
& =_\beta & & \lambda sx.\ s(s[s(sx)])  
& = & \underline{4}
& & | y := s(sx)\\
\endaligned
& = & & \underline{4}
\end{align}
</math></center>
</math></center>


Linia 378: Linia 375:




<center><math>\aligned
<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}\!\!>)
\underline{1},
\underline{1},
\endaligned</math></center>
\end{align}</math></center>




Linia 390: Linia 387:


<center><math>
<center><math>
\aligned
\begin{align}
<0, 0> & \mbox{~}\\
<0, 0> & \ \\
<1, 0> & \mbox{~~~~~~krok 1}\\
<1, 0> & \ \ \ \  \mbox{   krok 1}\\
<2, 1> & \mbox{~~~~~~{krok 2}}\\
<2, 1> & \ \ \ \  \mbox{   krok 2}\\
... \\
... \\
<n, n-1> & \mbox{~~~~~~krok } n
<n, n-1> & \ \ \ \ \mbox{ krok } n
\endaligned
\end{align}
</math></center>
</math></center>


Linia 408: Linia 405:


<center><math>
<center><math>
\aligned
\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},
\underline{0}\!\!>)\underline{1}\}\underline{2}\\
\underline{0}\!\!>)\underline{1}\}\underline{2}\\
& =_\beta & (\underline{2} (\lambda z. <\!\! A\underline{1}
& =_\beta & & (\underline{2} (\lambda z. <\!\! A\underline{1}
(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{\tiny procedura licząca 2}})
& = & & ((\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{\tiny następnik}})
(z\underline{0}) \!\!>}_{\mbox{ następnik}})
\underbrace{<\!\!\underline{0}, \underline{0}\!\!>}_{\mbox{\tiny zero}})
\underbrace{<\!\!\underline{0}, \underline{0}\!\!>}_{\mbox{ zero}})
\underline{1}\\
\underline{1}\\
& =_\beta & ((\lambda z. <\!\! A\underline{1}
& =_\beta & & ((\lambda z. <\!\! A\underline{1}
(z\underline{0}), (z\underline{0}) \!\!> )
(z\underline{0}), (z\underline{0}) \!\!> )
[(\lambda z. <\!\! A\underline{1}
[(\lambda z. <\!\! A\underline{1}
(z\underline{0}), (z\underline{0}) \!\!> )
(z\underline{0}), (z\underline{0}) \!\!> )
<\!\! \underline{0}, \underline{0} \!\!>])\underline{1}\\
<\!\! \underline{0}, \underline{0} \!\!>])\underline{1}\\
& =_\beta & ((\lambda z. <\!\! A\underline{1}
& =_\beta & & ((\lambda z. <\!\! A\underline{1}
(z\underline{0}), (z\underline{0}) \!\!> )
(z\underline{0}), (z\underline{0}) \!\!> )
<\!\! A\underline{1} \underline{0}, \underline{0} \!\!> )\underline{1}\\
<\!\! A\underline{1} \underline{0}, \underline{0} \!\!> )\underline{1}\\
& =_\beta & ((\lambda z. <\!\! A\underline{1}
& =_\beta & & ((\lambda z. <\!\! A\underline{1}
(z\underline{0}), (z\underline{0}) \!\!> )
(z\underline{0}), (z\underline{0}) \!\!> )
<\!\! \underline{1}, \underline{0} \!\!> )\underline{1}\\
<\!\! \underline{1}, \underline{0} \!\!> )\underline{1}\\
& =_\beta & ( <\!\! A\underline{1} \underline{1}, \underline{1}\!\!> )
& =_\beta & & ( <\!\! A\underline{1} \underline{1}, \underline{1}\!\!> )
\underline{1}\\
\underline{1}\\
& =_\beta & ( <\!\! \underline{2}, \underline{1}\!\!> )
& =_\beta & & ( <\!\! \underline{2}, \underline{1}\!\!> )
\underline{1}\\
\underline{1}\\
& =_\beta & \underline{1}
& =_\beta & & \underline{1}
\endaligned
\end{align}
</math></center>
</math></center>


Linia 451: Linia 448:




<center><math>{\cal Y}M =_\beta M({\cal Y}M).</math></center>
<center><math>{\cal Y}M =_\beta M({\cal Y}M)</math>.</center>




Linia 464: Linia 461:




<center><math>\aligned
<center><math>\begin{align}
\cal{Y} M & = & ( \lambda y. (\lambda x. y(xx))(\lambda x. y(xx)))M \qquad \text{z definicji} \cal{Y} \\
{\cal Y} M & = & &( \lambda y. (\lambda x. y(xx))(\lambda x. y(xx)))M  
& =_\beta & (\lambda x. M(xx))\underbrace{(\lambda x. M(xx))}_B \qquad y := M\\
& & | \mbox{ z definicji } {\cal Y} \\
& =_\beta & M(\underbrace{(\lambda x. M(xx))}_B \underbrace{(\lambda x. M(xx))}_B ) \qquad x := B\\
& =_\beta & & (\lambda x. M(xx))\underbrace{(\lambda x. M(xx))}_B  
& =_\beta & M( \cal{Y} M ) \qquad \cal{Y} M = (\lambda x. M(xx))(\lambda x. M(xx))\\
& &  | y := M\\
\endaligned
& =_\beta & & M(\underbrace{(\lambda x. M(xx))}_B \underbrace{(\lambda x. M(xx))}_B )  
& & | x := B\\
& =_\beta & & M( {\cal Y} M )  
& &  | {\cal Y} M = (\lambda x. M(xx))(\lambda x. M(xx))\\
\end{align}
</math></center>
</math></center>


Linia 499: Linia 500:
Typy atomowe oznaczamy najczęściej <math>\alpha</math>, <math>\beta</math>, <math>\gamma</math>, a&nbsp;typy ogólnie <math>\sigma</math>, <math>\tau</math>, ... Nawiasy są często pomijane zgodnie z&nbsp;zasadą łączności prawostronnej, tzn.  
Typy atomowe oznaczamy najczęściej <math>\alpha</math>, <math>\beta</math>, <math>\gamma</math>, a&nbsp;typy ogólnie <math>\sigma</math>, <math>\tau</math>, ... Nawiasy są często pomijane zgodnie z&nbsp;zasadą łączności prawostronnej, tzn.  


<center><math>\gamma\to \sigma\to \tau \equiv (\gamma\to (\sigma\to \tau)).</math></center>
<center><math>\gamma\to \sigma\to \tau \equiv (\gamma\to (\sigma\to \tau))</math>.</center>




Linia 517: 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> Rozstrzygalny jest także problem, czy danemu termowi przypisać można typ zgodnie z zasadami opisanymi powyżej.
<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 523: Linia 524:


<center><math>
<center><math>
\aligned
\begin{align}
\mbox{\bf Term:} & & \mbox{\bf Typ:}\\
\mathbf{ Term:} & & \mathbf{Typ:}\\
\hline
\hline
\lambda x : \alpha.x    & & \alpha\to \alpha\\
\lambda x : \alpha.x    & & \alpha\to \alpha\\
Linia 538: Linia 539:
\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>




Jeżeli <math>\tau_1,\ldots, \tau_n,</math> <math>\tau</math> są  typami, to zapis
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&nbsp;postaci <math>\tau_1, ..., \tau_n \to \alpha</math>, gdzie <math>n\ge 0</math>, a&nbsp;<math>\alpha</math>
w&nbsp;postaci <math>\tau_1, ..., \tau_n \to \alpha</math>, gdzie <math>n\ge 0</math>, a&nbsp;<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 , 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 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 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:



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 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.