Semantyka i weryfikacja programów/Ćwiczenia 1: Różnice pomiędzy wersjami
Nie podano opisu zmian |
Nie podano opisu zmian |
||
Linia 1: | Linia 1: | ||
== | == Zawartość == | ||
Tematem tych | Tematem tych zajęć jest semantyka operacyjna wyrażeń (małe kroki). | ||
== Semantyka operacyjna | == Semantyka operacyjna wyrażeń == | ||
{{cwiczenie|1|cw1| | {{cwiczenie|1|cw1| | ||
Rozważmy bardzo prosty język wyrażeń, którego składnia opisana jest | |||
następującą gramatyką: | |||
<math> | <math> | ||
Linia 30: | Linia 30: | ||
</math> | </math> | ||
Wynikiem | Wynikiem wyrażenienia warunkowego <math>\mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3</math> | ||
jest | jest wartość wyrażenia <math>e_2</math>, o ile wyrażenie | ||
<math>e_1</math> oblicza | <math>e_1</math> oblicza się do wartości różnej od zera; w przeciwnym | ||
przypadku wynikiem jest | przypadku wynikiem jest wartość wyrażenia <math>e_3</math>. | ||
Zaproponuj | Zaproponuj semantykę operacyjną (małe kroki) dla tego języka. | ||
}} | }} | ||
Linia 44: | Linia 44: | ||
Zacznijmy od ustalenia notacji i dziedzin syntaktycznych. | Zacznijmy od ustalenia notacji i dziedzin syntaktycznych. | ||
Niech <math>\mathbf{Num}</math> oznacza zbiór | Niech <math>\mathbf{Num}</math> oznacza zbiór stałych liczbowych, | ||
<math>n \in \mathbf{Num} = \{ 0, 1, \ldots \}</math>. | <math>n \in \mathbf{Num} = \{ 0, 1, \ldots \}</math>. | ||
Podobnie, niech <math>\mathbf{Var}</math> oznacza zbiór identyfikatorów, które | Podobnie, niech <math>\mathbf{Var}</math> oznacza zbiór identyfikatorów, które | ||
mogą być nazwami zmiennych; <math>x \in \mathbf{Var}</math>. | |||
Wreszcie, niech <math>\mathbf{Exp}</math> oznacza zbiór | Wreszcie, niech <math>\mathbf{Exp}</math> oznacza zbiór wyrażeń; | ||
<math>e \in \mathbf{Exp}</math>. | <math>e \in \mathbf{Exp}</math>. | ||
Dla | Dla ułatwienia zapisywania reguł zakładamy, ze stałe | ||
liczbowe | liczbowe są wyrażeniami, czyli <math>\mathbf{Num} \subseteq \mathbf{Exp}</math>. | ||
Będziemy potrzebować zbioru ''stanów'', opisujących wartości | |||
przypisane zmiennym. | przypisane zmiennym. | ||
Najprostszym | Najprostszym rozwiązaniem jest przyjąc, że stan to funkcja | ||
z <math>\mathbf{Var}</math> do <math>\mathbf{Num}</math>. | z <math>\mathbf{Var}</math> do <math>\mathbf{Num}</math>. | ||
Oznaczmy przez <math>\mathbf{State}</math> zbiór wszystkich takich funkcji; | Oznaczmy przez <math>\mathbf{State}</math> zbiór wszystkich takich funkcji; | ||
stany oznaczac | stany oznaczac będziemy przez <math>s, s_1, s', \ldots \in \mathbf{State}</math>. | ||
W naszej semantyce | W naszej semantyce będziemy potrzebowac tranzycji dwóch postaci. | ||
Po pierwsze, tranzycje postaci | Po pierwsze, tranzycje postaci | ||
Linia 67: | Linia 67: | ||
</math> | </math> | ||
oznaczające mały krok w trakcie obliczania wyrażenia <math>e</math> | |||
w stanie <math>s</math>, w wyniku którego <math>e</math> | w stanie <math>s</math>, w wyniku którego <math>e</math> wyewoluowało do | ||
<math>e'</math>. Stan nie ulega zmiania podczas obliczania | <math>e'</math>. Stan nie ulega zmiania podczas obliczania wyrażenia | ||
(nie ma tzw. ''efektów ubocznych''), | (nie ma tzw. ''efektów ubocznych''), | ||
więc to samo <math>s</math> figuruje po lewej i prawej stronie strzałki. | |||
Po drugie, tranzycje postaci | Po drugie, tranzycje postaci | ||
Linia 78: | Linia 78: | ||
</math> | </math> | ||
będą oznaczaczać, że wyrażenie <math>e</math> jest już policzone, | |||
a jego | a jego wartością jest <math>n</math>. | ||
Zatem przyjmijmy, | Zatem przyjmijmy, że zbiór konfiguracji to | ||
<math> | <math> | ||
Linia 87: | Linia 87: | ||
</math> | </math> | ||
a konfiguracje | a konfiguracje końcowe to <math>\mathbf{Num}</math>. | ||
{{ | {{ | ||
uwaga||uwaga1| | uwaga||uwaga1| | ||
Tak | Tak naprawdę to druga postać tranzycji nie jest | ||
niezbędna, gdyż moglibyśmy umówić się, że konfiguracje końcowe | |||
to <math>\mathbf{Num} \times \mathbf{State}</math>. | to <math>\mathbf{Num} \times \mathbf{State}</math>. | ||
}} | }} | ||
Najprostsze | Najprostsze są tranzycje prowadzące do konfiguracji końcowej: | ||
<math> | <math> | ||
Linia 102: | Linia 102: | ||
</math> | </math> | ||
Symbol <math>n</math> po lewej stronie to | Symbol <math>n</math> po lewej stronie to wyrażenie składające się | ||
ze | ze stałej liczbowej, podczas gdy <math>n</math> po prawej stronie reprezentuje liczbę | ||
będącą wartością wyrażenia. | |||
Zmienna oblicza | Zmienna oblicza się do swojej wartości w bieżącym stanie: | ||
<math> | <math> | ||
Linia 112: | Linia 112: | ||
</math> | </math> | ||
Teraz zajmiemy | Teraz zajmiemy się dodawaniem <math>e_1 + e_2</math>. Ponieważ semantyka jest w stylu małych | ||
kroków, musimy | kroków, musimy zdecydować się czy najpierw obliczyć pierwszy (lewy) składnik | ||
<math>e_1</math> czy drugi? | <math>e_1</math> czy drugi? | ||
Jeśli wybierzemy lewy (strategia ''lewostronna''), otrzymamy regułę: | |||
<math> | <math> | ||
Linia 123: | Linia 123: | ||
</math> | </math> | ||
Reguły tej postaci będziemy zapisywać tak: | |||
<math> | <math> | ||
Linia 130: | Linia 130: | ||
</math> | </math> | ||
Czyli | Czyli mały krok w <math>e_1</math> stanowi też mały krok w <math>e_1 + e_2</math>. | ||
Po | Po zakończeniu obliczania <math>e_1</math> przechodzimy do <math>e_2</math>: | ||
<math> | <math> | ||
Linia 138: | Linia 138: | ||
</math> | </math> | ||
A na | A na końcu dodajemy: | ||
<math> | <math> | ||
Linia 144: | Linia 144: | ||
</math> | </math> | ||
Zwróćmy tutaj uwagę na pewną subtelność, dotyczącą dwóch wystąpień | |||
symbolu <math>+</math>: pierwsze | symbolu <math>+</math>: pierwsze wystąpienie oznacza jedną z konstrukcji składniowych | ||
języka, a drugie oznacza operację dodawania w zbiorze <math>\mathbf{Num}</math>. | |||
Pozwalamy sobie na | Pozwalamy sobie na taką kolizję oznaczeń, gdyż nie powinna ona | ||
prowadzić do niejednoznaczności. Pamiętajmy, że składnia języka jest | |||
składnią abstrakcyjną, więc zamiast <math>e_1 + e_2</math> moglibyśmy równie | |||
dobrze | dobrze pisać np. <math>{\mathrm{add}}(e_1, e_2)</math> a wtedy reguła | ||
wyglądałaby tak: | |||
<math> | <math> | ||
Linia 157: | Linia 157: | ||
</math> | </math> | ||
Inną możliwą strategią obliczania <math>e_1 + e_2</math> jest strategia | |||
''prawostronna'', | ''prawostronna'', którą otrzymujemy zastępując pierwsze dwie z trzech | ||
powyższych reguł przez: | |||
<math> | <math> | ||
Linia 169: | Linia 169: | ||
</math> | </math> | ||
Ponadto, | Ponadto, jeśli przyjmiemy regułę pierwszą (dla <math>e_1</math>), trzecią | ||
i | i czwartą (dla <math>e_2</math>), otrzymamy strategię | ||
'' | ''równoległą'', polegającą na obliczaniu jednocześnie <math>e_1</math> i | ||
<math>e_2</math>: | <math>e_2</math>: | ||
Linia 184: | Linia 184: | ||
</math> | </math> | ||
Bardziej precyzyjnie | Bardziej precyzyjnie mówiąc, małe kroki obliczające | ||
obydwa | obydwa podwyrażenia przeplatają się, i to w dowolny sposób. | ||
Ta | Ta dowolność prowadzi do ''niedeterminizmu'', czyli do sytuacji, gdy | ||
kolejna ( | kolejna (następna) konfiguracja nie jest wyznaczona jednoznacznie. | ||
Jest tak, | Jest tak, gdyż możemy mieć do wyboru dwie różne tranzycje | ||
<math> | <math> | ||
Linia 196: | Linia 196: | ||
</math> | </math> | ||
Zauważmy natomiast, że kolejność przeplatania się małych kroków obliczających | |||
<math>e_1</math> i <math>e_2</math> nie | <math>e_1</math> i <math>e_2</math> nie wpływa w tym przypadku na końcową wartość | ||
całego wyrażenia. | |||
Na koniec | Na koniec reguły dla wyrażenia warunkowego. | ||
<math> | <math> | ||
Linia 221: | Linia 221: | ||
{{cwiczenie|2|cw2| | {{cwiczenie|2|cw2| | ||
Rozszerzmy | Rozszerzmy język wyrażeń z poprzedniego zadania o jedną konstrukcję | ||
<math> | <math> | ||
Linia 229: | Linia 229: | ||
</math> | </math> | ||
Wyrażenie <math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math> zawiera w sobie deklarację | |||
<math>x = e_1</math>, która stanowi mechanizm | <math>x = e_1</math>, która stanowi mechanizm wiązania | ||
identyfikatorów w naszym | identyfikatorów w naszym języku. | ||
Deklaracja <math>x = e_1</math> wprowadza | Deklaracja <math>x = e_1</math> wprowadza nową zmienną <math>x</math> | ||
oraz przypisuje jej | oraz przypisuje jej wartość. | ||
Wartość wyrażenia <math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math> obliczamy następująco: | |||
najpierw oblicza | najpierw oblicza się wartość <math>e_1</math>, podstawia ją na zmienna | ||
<math>x</math>, a | <math>x</math>, a następnie oblicza wyrażenie <math>e_2</math>. | ||
Zakresem zmiennej <math>x</math> jest | Zakresem zmiennej <math>x</math> jest wyrażenie <math>e_2</math>, czyli | ||
wewnątrz <math>e_2</math> można odwoływać się (wielokrotnie) do zmiennej <math>x</math>; | |||
Ogólniej, | Ogólniej, odwołania do zmiennej w wyrażeniu odnoszą się do ''najbliższej'' | ||
(najbardziej | (najbardziej zagnieżdzonej) deklaracji tej zmiennej. | ||
Taki mechanizm | Taki mechanizm wiązania identyfikatorów nazywamy ''wiązaniem | ||
statycznym''. | statycznym''. | ||
Przyjmujemy | Przyjmujemy zwykłe (statyczne) reguły przesłaniania zmiennych, np. | ||
jeśli w <math>e_2</math> występuje podwyrażenie <math>\mathbf{let}\, x = e \,\mathbf{in}\, e'</math> to | |||
deklaracja <math>x = e</math> '' | deklaracja <math>x = e</math> ''przesłania'' deklarację <math>x = e_1</math> | ||
w | w wyrażeniu <math>e'</math>. | ||
Zakładamy, że na początku wartości wszystkich zmiennych są | |||
'' | ''nieokreślone'', czyli zmienne są niezainicjowane, a odwołanie do | ||
niezainicjowanej zmiennej jest | niezainicjowanej zmiennej jest uważane za niepoprawne. | ||
}} | }} | ||
Linia 268: | Linia 268: | ||
<math> | <math> | ||
\mathbf{let}\, z = 5 \,\mathbf{in}\, x+z | \mathbf{let}\, z = 5 \,\mathbf{in}\, x+z | ||
\quad \quad \mapsto \quad \quad \mbox{brak wyniku; | \quad \quad \mapsto \quad \quad \mbox{brak wyniku; odwołanie do niezainicjowanej | ||
zmiennej}\, x | zmiennej}\, x | ||
</math> | </math> | ||
Linia 284: | Linia 284: | ||
Podobnie jak poprzednio, | Podobnie jak poprzednio, | ||
stan powinien | stan powinien opisywać wartości przypisane zmiennym. | ||
Tym razem jednak | Tym razem jednak | ||
uwzględnimy niezainicjowane zmienne, czyli zmienne bez żadnej wartości. | |||
Przyjmijmy zatem, | Przyjmijmy zatem, że stan to skończona funkcja częściowa z <math>\mathbf{Var}</math> do <math>\mathbf{Num}</math>. | ||
Oznaczmy symbolem <math>\mathbf{State}</math> zbiór wszystkich takich funkcji: | Oznaczmy symbolem <math>\mathbf{State}</math> zbiór wszystkich takich funkcji: | ||
<math> | <math> | ||
\mathbf{State} = \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Num} | \mathbf{State} = \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Num} | ||
</math>. | </math>. | ||
Naturalnym stanem | Naturalnym stanem początkowym jest stan ''pusty'', tzn. | ||
pusta funkcja | pusta funkcja częściowa, który będziemy oznaczać symbolem <math>\emptyset</math>. | ||
Wartość wyrażenia <math>e</math> w stanie początkowym wynosi <math>n</math> | |||
o ile zachodzi: | o ile zachodzi: | ||
Linia 301: | Linia 301: | ||
</math> | </math> | ||
Będziemy potrzebowac tranzycji dwóch postaci, podobnie jak poprzednio, | |||
ale pierwsza | ale pierwsza postać będzie trochę ogólniejsza: | ||
<math> | <math> | ||
Linia 308: | Linia 308: | ||
</math> | </math> | ||
Tranzycja ta oznacza | Tranzycja ta oznacza mały krok w trakcie obliczania wyrażenia <math>e</math> | ||
w stanie <math>s</math>, w wyniku którego <math>e</math> | w stanie <math>s</math>, w wyniku którego <math>e</math> wyewoluowało do | ||
<math>e'</math> a nowym stanem jest <math>s'</math>. | <math>e'</math> a nowym stanem jest <math>s'</math>. | ||
Stan | Stan może się teraz zmienić na skutek deklaracji zmiennych. | ||
Spróbujmy rozszerzyc | Spróbujmy rozszerzyc semantykę z poprzedniego zadania. | ||
Ponieważ stan jest funkcją częściową, musimy zmienić niektóre reguły, np. | |||
<math> | <math> | ||
x, s \,\Longrightarrow\, n, s \quad \mbox{ o ile } s(x) \mbox{ jest | x, s \,\Longrightarrow\, n, s \quad \mbox{ o ile } s(x) \mbox{ jest określone i } s(x) = n | ||
</math> | </math> | ||
Następnie dodajemy reguły dla wyrażenia | |||
<math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math>. | <math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math>. | ||
Gdy <math>e_1</math> jest | Gdy <math>e_1</math> jest już obliczne, wyatarczy reguła: | ||
<math> | <math> | ||
Linia 329: | Linia 329: | ||
Notacja <math>s[x \mapsto n]</math> oznacza stan <math>s</math>, który zmodyfikowano | Notacja <math>s[x \mapsto n]</math> oznacza stan <math>s</math>, który zmodyfikowano | ||
przypisując zmiennej <math>x</math> wartość <math>n</math>, | |||
niezależnie od tego, czy <math>s(x)</math> było określone, czy nie, | |||
i | i pozostawiając niezmienione wartości dla pozostałych zmiennych. | ||
Formanie | Formanie | ||
Linia 342: | Linia 342: | ||
</math> | </math> | ||
W | W szczególności, | ||
dla <math>y \neq x</math>, <math>s[x \mapsto n](y)</math> jest | dla <math>y \neq x</math>, <math>s[x \mapsto n](y)</math> jest określone wtedy i tylko | ||
wtedy, gdy <math>s(y)</math> jest | wtedy, gdy <math>s(y)</math> jest określone. | ||
Natomiast aby obliczyc <math>e_1</math> potrzebujemy | Natomiast aby obliczyc <math>e_1</math> potrzebujemy reguły: | ||
<math> | <math> | ||
Linia 353: | Linia 353: | ||
</math> | </math> | ||
Zwróćmy uwagę, że stan <math>s'</math> może być różny od <math>s</math>, | |||
np. dlatego, | np. dlatego, że wewnątrz <math>e_1</math> znajduje się podwyrażenie | ||
<math>\mathbf{let}\, y = \ldots</math>. | <math>\mathbf{let}\, y = \ldots</math>. | ||
'''Pytanie:''' czy taka semantyka jest poprawna? | '''Pytanie:''' czy taka semantyka jest poprawna? | ||
Niestety nie, | Niestety nie, gdyż nie uwzględniamy ograniczonego zasięgu zmiennej. | ||
Rzućmy okiem na przykład: | |||
<math> | <math> | ||
Linia 366: | Linia 366: | ||
</math> | </math> | ||
Według naszych intencji to wyrażenie nie ma wartości, gdyż ostatnie | |||
odwołanie do <math>z</math> jest błędne. | |||
Natomiast | Natomiast według powyższych reguł mamy | ||
<math> | <math> | ||
Linia 378: | Linia 378: | ||
</math> | </math> | ||
Nasz | Nasz błąd polega na tym, że po zakończeniu obliczania podwyrażenia | ||
<math>\mathbf{let}\, z = 4 \,\mathbf{in}\, z+z+z</math> ''zapominamy'' | <math>\mathbf{let}\, z = 4 \,\mathbf{in}\, z+z+z</math> ''zapominamy'' przywrócić zmiennej <math>z</math> | ||
poprzednią wartość (a właściwie brak wartości w przykładzie powyżej). | |||
Przedyskutujmy kilka wariantów. | Przedyskutujmy kilka wariantów. | ||
Linia 388: | Linia 388: | ||
<br> | <br> | ||
Wygodne i eleganckie | Wygodne i eleganckie rozwiązanie tego problemu jest możliwe, jeśli | ||
rozszerzymy | rozszerzymy składnię naszego języka. | ||
Intuicyjnie, | Intuicyjnie, reguła | ||
<math> | <math> | ||
Linia 396: | Linia 396: | ||
</math> | </math> | ||
powinna | powinna zostać zastąpiona przez | ||
<math> | <math> | ||
\mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow\, e_2 \,\mathbf{then}\, \mbox{,, | \mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow\, e_2 \,\mathbf{then}\, \mbox{,,przywróć wartość zmiennej x''}, s[x \mapsto n]. | ||
</math> | </math> | ||
czyli potrzebujemy konstrukcji | czyli potrzebujemy konstrukcji składniowej, która polega na obliczeniu | ||
wyrażenia | |||
<math>e_2</math> a | <math>e_2</math> a następnie na przypisaniu zmiennej <math>x</math> danej wartości. | ||
Rozszerzmy zatem | Rozszerzmy zatem składnię następujaco: | ||
<math> | <math> | ||
Linia 413: | Linia 413: | ||
</math> | </math> | ||
Wyrażenie <math>e \,\mathbf{then}\, x:= n</math> jest w pewnym sensie dualne | |||
do <math>\mathbf{let}\, x = n \,\mathbf{in}\, e</math>, | do <math>\mathbf{let}\, x = n \,\mathbf{in}\, e</math>, gdyż jedyna (choc niewątpliwie istotna) różnica | ||
między nimi to kolejność obliczenia <math>e</math> i przypisania wartości | |||
na | na zmienną <math>x</math>. | ||
Oto nowa | Oto nowa reguła | ||
<math> | <math> | ||
Linia 424: | Linia 424: | ||
</math> | </math> | ||
Pewna | Pewna trudność pojawia się w sytuacji, gdy <math>s(x)</math> jest | ||
nieokreślone, czyli gdy zmienna <math>x</math> jest niezainicjowana -- reguła | |||
powyższa nie obejmuje wogóle takiej sytuacji. | |||
Najprostszym sposobem | Najprostszym sposobem rozwiązania tej trudności jest rozszerzenie | ||
konstrukcji <math>e \,\mathbf{then}\, x := n</math>: | konstrukcji <math>e \,\mathbf{then}\, x := n</math>: | ||
Linia 437: | Linia 437: | ||
</math> | </math> | ||
gdzie symbol <math>\bot</math> oznacza brak | gdzie symbol <math>\bot</math> oznacza brak wartości. | ||
Dodajemy | Dodajemy również regułę: | ||
<math> | <math> | ||
\mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow\, e_2 \,\mathbf{then}\, x := \bot, s[x \mapsto n] \quad | \mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow\, e_2 \,\mathbf{then}\, x := \bot, s[x \mapsto n] \quad | ||
\mbox{ o ile } s(x) \, \mbox{ jest | \mbox{ o ile } s(x) \, \mbox{ jest nieokreślone}. | ||
</math> | </math> | ||
Rozwiązanie to jest odrobinę nieeleganckie, gdyż prawie identyczne reguły | |||
musimy | musimy napisać dwukrotnie. | ||
Widać to np. w poniższych regułach, ''scalających'' semantykę dla <math>e \,\mathbf{then}\, x := n</math> | |||
z | z semantyką pozostałych wyrażeń: | ||
<math> | <math> | ||
Linia 461: | Linia 461: | ||
<math> | <math> | ||
n' \,\mathbf{then}\, x := \bot, s \,\Longrightarrow\, n', s' \quad \mbox{ o ile } s(x) | n' \,\mathbf{then}\, x := \bot, s \,\Longrightarrow\, n', s' \quad \mbox{ o ile } s(x) | ||
\mbox{ jest | \mbox{ jest określone i } s' = s \setminus \{ (x, s(x)) \} | ||
</math> | </math> | ||
Linia 469: | Linia 469: | ||
<br> | <br> | ||
Zanim przejdziemy do kolejnego wariantu, zastanówmy | Zanim przejdziemy do kolejnego wariantu, zastanówmy się czy | ||
istnieje inny sposób | istnieje inny sposób rozwiązania trudności związanej z <math>n = | ||
\bot</math>, który | \bot</math>, który pozwalałby uniknąć wprowadzania dodatkowej konstrukcji | ||
<math>e \,\mathbf{then}\, x := \bot</math>. | <math>e \,\mathbf{then}\, x := \bot</math>. | ||
Pomysł może polegać na rozszerzeniu | |||
zbioru <math>\mathbf{Num}</math> o dodatkowy element <math>\bot</math>: | zbioru <math>\mathbf{Num}</math> o dodatkowy element <math>\bot</math>: | ||
Linia 480: | Linia 480: | ||
</math> | </math> | ||
Wtedy nie musimy | Wtedy nie musimy pisać dwóch bardzo podobnych wariantów reguł. | ||
Dodatkowo, w tym | Dodatkowo, w tym rozwiązaniu warto poczynić umowę, że | ||
<math>s(x) = \bot</math> reprezentuje brak | <math>s(x) = \bot</math> reprezentuje brak wartości zmiennej <math>x</math>. | ||
Wtedy stany | Wtedy stany są funkcjami całkowitymi z <math>\mathbf{Var}</math> w <math>\mathbf{Num}</math> | ||
przyjmującymi wartość różną od <math>\bot</math> tylko dla skończenie | |||
wielu elementów. | wielu elementów. | ||
Pewnym mankamentem jest to, | Pewnym mankamentem jest to, że teraz | ||
<math>n = \bot</math> | <math>n = \bot</math> może pojawiać sie w wyrażeniach podobnie jak stałe. | ||
Tym niemniej nie musimy | Tym niemniej nie musimy adaptować reguł dla stałych tak, aby radziły | ||
one sobie z <math>n = \bot</math>, | one sobie z <math>n = \bot</math>, ponieważ wyrażenia zawierające | ||
<math>\bot</math> | <math>\bot</math> możemy również uważać za roszerzenie składni. | ||
Jeśli jednak dopuścimy symbol <math>\bot</math> w wyrażeniach, to możemy | |||
elegancko | elegancko wybrnąć z sytuacji, rozszerzając operacje arytmetyczne | ||
na zbiór <math>\mathbf{Num} \cup \{ \bot \}</math> tak, aby | na zbiór <math>\mathbf{Num} \cup \{ \bot \}</math> tak, aby zachowywały one | ||
nieokreśloność: | |||
<math> | <math> | ||
Linia 501: | Linia 501: | ||
</math> | </math> | ||
Trzeba jednak w takim razie | Trzeba jednak w takim razie zadbać o to, aby wyrażenie <math>\mathbf{let}\, x = e_1 | ||
\,\mathbf{in}\, e_2</math> | \,\mathbf{in}\, e_2</math> obliczało się normalnie tylko wtedy, gdy wartość | ||
wyrażenia <math>e_1</math> jest różna od <math>\bot</math>. | |||
Linia 510: | Linia 510: | ||
<br> | <br> | ||
Zrewidujmy teraz podstawowe | Zrewidujmy teraz podstawowe założenia, które dotychczas poczyniliśmy. | ||
Jednym z nich | Jednym z nich było przyjęcie ogólnej postaci tranzycji: | ||
<math> | <math> | ||
Linia 517: | Linia 517: | ||
</math> | </math> | ||
pozwalającej na zmianę stanu podczas obliczania wyrażenia. | |||
Czy faktycznie | Czy faktycznie był to dobry pomysł? Czy moglibyśmy poradzić sobie | ||
przy pomocy tranzycji postaci | przy pomocy tranzycji postaci | ||
Linia 525: | Linia 525: | ||
</math> | </math> | ||
Spróbujmy! Oto nowa wersja jednej z | Spróbujmy! Oto nowa wersja jednej z reguł dla <math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math> | ||
dotycząca kroku wewnątrz <math>e_1</math>: | |||
<math> | <math> | ||
Linia 533: | Linia 533: | ||
</math> | </math> | ||
Dotychczas nie ma problemu: | Dotychczas nie ma problemu: podwyrażenie <math>e_1</math> jest | ||
prawidłowo obliczane w stanie <math>s</math>. Trudność pojawi się, gdy | |||
zakończymy obliczanie <math>e_1</math> i przejdziemy do <math>e_2</math>. | |||
Oto | Oto możliwa reguła: | ||
<math> | <math> | ||
Linia 543: | Linia 543: | ||
</math> | </math> | ||
Okazuje | Okazuje się, że wszystko jest w porządku. Wyrażenie <math>e</math> | ||
obliczamy w | obliczamy w prawidłowym stanie, tzn. z wartością <math>n</math> | ||
przypisaną zmiennej <math>x</math>. | |||
Mały krok w <math>e</math> daje przyczynek do małego kroku w całym | |||
wyrażeniu, a przy tym stan pozostaje niezmieniony. | |||
Przy tym wogóle nie potrzebujemy | Przy tym wogóle nie potrzebujemy przywracać poprzedniej wartości | ||
zmiennej <math>x</math>, | zmiennej <math>x</math>, ponieważ <math>x</math> zyskuje nową wartość | ||
''tylko'' na potrzeby obliczania | ''tylko'' na potrzeby obliczania podwyrażenia <math>e</math>! | ||
Można na to również spojrzeć inaczej: informacja o nowej wartości | |||
<math>n</math> dla zmiennej <math>x</math> nie jest jawnie dodawana do stanu | <math>n</math> dla zmiennej <math>x</math> nie jest jawnie dodawana do stanu | ||
<math>s</math>, ale jest przechowywana w | <math>s</math>, ale jest przechowywana w składni wyrażenia | ||
<math>\mathbf{let}\, x = n \,\mathbf{in}\, \ldots</math> jako deklaracja <math>x = n</math>. | <math>\mathbf{let}\, x = n \,\mathbf{in}\, \ldots</math> jako deklaracja <math>x = n</math>. | ||
Na | Na końcu musimy oczywiście pozbyć się tej deklaracji za pomocą | ||
następującej tranzycji: | |||
<math> | <math> | ||
Linia 562: | Linia 562: | ||
</math> | </math> | ||
Podsumujmy. Okazuje | Podsumujmy. Okazuje się, że rozwiązanie nie było wcale łatwe, | ||
nawet dla tak | nawet dla tak prościutkiego języka. W przyszłości przekonamy się, | ||
że łatwiej jest poradzić sobie z zagadnieniem wiązania identyfikatorów | |||
w semantyce naturalnej ( | w semantyce naturalnej (duże kroki). | ||
W wariancie 1 i 2 | W wariancie 1 i 2 wprowadziliśmy do języka dodatkowe elementy, tak, | ||
by | by łatwiej było pisać reguły. W przyszłości będziemy czasem stosować | ||
takie | takie podejście. | ||
Niekiedy jednak rozszerzanie | Niekiedy jednak rozszerzanie języka będzie zabronione. | ||
</div></div> | </div></div> | ||
Linia 586: | Linia 586: | ||
{{cwiczenie|2|cw2.dom| | {{cwiczenie|2|cw2.dom| | ||
Dotychczas | Dotychczas wystąpienie błędu podczas obliczania wyrażenia, | ||
np. | np. odwołanie do niezainicjowanej zmiennej, powodowało, że | ||
wyrażenie nie posiadało wartości (nie było ciągu tranzycji | |||
prowadzących do konfiguracji końcowej). Zmodyfikuj którąś z semantyk | |||
z poprzednich | z poprzednich zadań tak, aby błąd był komunikowany | ||
jako jedna z konfiguracji | jako jedna z konfiguracji końcowych. To znaczy: jeśli obliczenie | ||
wyrażenia <math>e</math> w stanie <math>s</math> jest niemożliwe bo wystąpił | |||
błąd, to | |||
<math> | <math> | ||
Linia 603: | Linia 603: | ||
{{cwiczenie|3|cw3.dom| | {{cwiczenie|3|cw3.dom| | ||
Rozważ rozszerzenie języka wyrażeń o wyrażenia boolowskie: | |||
<math> | <math> | ||
Linia 632: | Linia 632: | ||
</math> | </math> | ||
Zaproponuj | Zaproponuj semantykę małych kroków dla tego języka. | ||
Rozważ różne strategie obliczania wyrażeń boolowskich, oraz podejście leniwe. | |||
Na | Na przykład w strategii lewostronnej dla <math>b_1 \land b_2</math>, | ||
gdy <math>b_1</math> | gdy <math>b_1</math> zostało obliczone do <math>\mathbf{false}</math>, w podejściu leniwym nie ma wogóle potrzeby | ||
obliczania <math>b_2</math>. | obliczania <math>b_2</math>. | ||
}} | }} |
Wersja z 08:04, 10 sie 2006
Zawartość
Tematem tych zajęć jest semantyka operacyjna wyrażeń (małe kroki).
Semantyka operacyjna wyrażeń
Ćwiczenie 1
Rozważmy bardzo prosty język wyrażeń, którego składnia opisana jest następującą gramatyką:
Wynikiem wyrażenienia warunkowego jest wartość wyrażenia , o ile wyrażenie oblicza się do wartości różnej od zera; w przeciwnym przypadku wynikiem jest wartość wyrażenia .
Zaproponuj semantykę operacyjną (małe kroki) dla tego języka.
Rozwiązanie
Ćwiczenie 2
Rozszerzmy język wyrażeń z poprzedniego zadania o jedną konstrukcję
Wyrażenie zawiera w sobie deklarację , która stanowi mechanizm wiązania identyfikatorów w naszym języku. Deklaracja wprowadza nową zmienną oraz przypisuje jej wartość. Wartość wyrażenia obliczamy następująco: najpierw oblicza się wartość , podstawia ją na zmienna , a następnie oblicza wyrażenie . Zakresem zmiennej jest wyrażenie , czyli wewnątrz można odwoływać się (wielokrotnie) do zmiennej ; Ogólniej, odwołania do zmiennej w wyrażeniu odnoszą się do najbliższej (najbardziej zagnieżdzonej) deklaracji tej zmiennej. Taki mechanizm wiązania identyfikatorów nazywamy wiązaniem statycznym. Przyjmujemy zwykłe (statyczne) reguły przesłaniania zmiennych, np. jeśli w występuje podwyrażenie to deklaracja przesłania deklarację w wyrażeniu .
Zakładamy, że na początku wartości wszystkich zmiennych są nieokreślone, czyli zmienne są niezainicjowane, a odwołanie do niezainicjowanej zmiennej jest uważane za niepoprawne.
Przykład
Parser nie mógł rozpoznać (błąd składni): {\displaystyle \mathbf{let}\, z = 5 \,\mathbf{in}\, x+z \quad \quad \mapsto \quad \quad \mbox{brak wyniku; odwołanie do niezainicjowanej zmiennej}\, x }
Rozwiązanie
Zadania domowe
Ćwiczenie 1
Zapisz wariant 2 semantyki z poprzedniego zadania.
Ćwiczenie 2
Dotychczas wystąpienie błędu podczas obliczania wyrażenia, np. odwołanie do niezainicjowanej zmiennej, powodowało, że wyrażenie nie posiadało wartości (nie było ciągu tranzycji prowadzących do konfiguracji końcowej). Zmodyfikuj którąś z semantyk z poprzednich zadań tak, aby błąd był komunikowany jako jedna z konfiguracji końcowych. To znaczy: jeśli obliczenie wyrażenia w stanie jest niemożliwe bo wystąpił błąd, to
Ćwiczenie 3
Rozważ rozszerzenie języka wyrażeń o wyrażenia boolowskie:
Zaproponuj semantykę małych kroków dla tego języka. Rozważ różne strategie obliczania wyrażeń boolowskich, oraz podejście leniwe. Na przykład w strategii lewostronnej dla , gdy zostało obliczone do , w podejściu leniwym nie ma wogóle potrzeby obliczania .