Semantyka i weryfikacja programów/Ćwiczenia 1: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Sl (dyskusja | edycje)
Nie podano opisu zmian
m Zastępowanie tekstu – „.↵</math>” na „</math>”
 
(Nie pokazano 23 wersji utworzonych przez 5 użytkowników)
Linia 1: Linia 1:
== Ćwiczenia 1: Semantyka operacyjna wyrażeń ==
== Zawartość ==


Tematem tych zajęć jest semantyka operacyjna wyrażeń (małe kroki).
== Semantyka operacyjna wyrażeń ==
{{cwiczenie|1|cw1|
Rozważmy bardzo prosty język wyrażeń, którego składnia opisana jest następującą gramatyką:


==== Zadanie 1 ====
Semantyka języka Tiny z wykładu używała funkcji semantycznych
<math>
<math>
B, E : State \to State
n \, ::= \,\, 0 \,\,|\,\, 1 \,\,|\,\, \ldots
</math>
</math>
dla określenie znaczenia wyrażeń boolowskich i arytmetycznych.
Zdefiniuj znaczenie wyrażeń za pomocą semantyki operacyjnej,
w stylu dużych kroków (semantyka naturalna) i małych kroków.
==== Rozwiązanie ====
Przypomnijmy składnię wyrażeń boolowskich i arytmetycznych:


<math>
<math>
b \, ::= \,\,
x \, ::= \,\, \ldots \, (identyfikatory) \, \ldots
        true  \,\,|\,\,
        false  \,\,|\,\,
        e_1 \leq e_2  \,\,|\,\,
        \neg b  \,\,|\,\,
        b_1  \land  b_2  \,\,|\,\,
        b_1  \lor  b_2
</math>
</math>


<math>
<math>
e \,  ::= \,\,   
e \,  ::= \,\,   
         0  \,\,|\,\,  1    \,\,|\,\,  \ldots   \,\,|\,\,
         n   \,\,|\,\,
         x \,\,|\,\,
         x   \,\,|\,\,
         e_1 + e_2 \,\,|\,\,
         e_1 + e_2   \,\,|\,\,
         e_1 * e_2  \,\,|\,\,
         \mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3
        e_1 - e_2 \,\,|\,\,  \ldots
</math>
</math>


Wynikiem wyrażenienia warunkowego <math>\mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3</math> jest wartość wyrażenia <math>e_2</math>, o ile wyrażenie <math>e_1</math> oblicza się do wartości różnej od zera; w przeciwnym przypadku wynikiem jest wartość wyrażenia <math>e_3</math>.
Zaproponuj semantykę operacyjną (małe kroki) dla tego języka.
}}
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span>
<div class="mw-collapsible-content" style="display:none">


Zacznijmy od dużych kroków.
Zacznijmy od ustalenia notacji i dziedzin syntaktycznych.
Niech <math>\mathbf{Num}</math> oznacza zbiór stałych liczbowych, <math>n \in \mathbf{Num} = \{ 0, 1, \ldots \}</math>.
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 wyrażeń; <math>e \in \mathbf{Exp}</math>.
Dla ułatwienia zapisywania reguł zakładamy, że stałe  liczbowe są wyrażeniami, czyli <math>\mathbf{Num} \subseteq \mathbf{Exp}</math>.


===== Semantyka naturalna =====
Będziemy potrzebować zbioru "stanów", opisujących wartości przypisane zmiennym.
Najprostszym rozwiązaniem jest przyjąć, że stan to funkcja z <math>\mathbf{Var}</math> do <math>\mathbf{Num}</math>.
Oznaczmy przez <math>\mathbf{State}</math> zbiór wszystkich takich funkcji; stany oznaczać będziemy przez <math>s, s_1, s', \ldots \in \mathbf{State}</math>.


Chcemy, aby tranzycje wyrażen wyglądały następująco:
W naszej semantyce będziemy potrzebowac tranzycji dwóch postaci.
Po pierwsze, tranzycje postaci


<math>
<math>
e, s \longrightarrow n 
e, s \,\Longrightarrow, e', s
\quad \quad \quad
b, s \longrightarrow l,
</math>
</math>


gdzie
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> wyewoluowało do <math>e'</math>.  
<math>s \in State</math>, <math>n</math> jest liczbą całkowitą,
Stan nie ulega zmianie podczas obliczania wyrażenia (nie ma tzw. ''efektów ubocznych''), więc to samo <math>s</math> figuruje po lewej i prawej stronie strzałki.
<math>n \in Int</math>, a <math>l \in Bool = \{ true, false \}</math>.
Tranzycja taka oznacza, że wyrażenie <math>e</math> w stanie <math>s</math> wylicza się do
wartości <math>n</math>, oraz że wyrażenie logiczne <math>b</math> w
stanie <math>s</math> wylicza się do <math>l</math>.
Zauważmy, że zakładamy tu, iż obliczenie wyrażenia nie zmienia
stanu (nie ma efektów ubocznych).


W tym celu rozszerzamy zbiór konfiguracji <math>\Gamma</math> następująco:
Po drugie, tranzycje postaci


<math>
<math>
\Gamma = (Instr \times State) \cup (Expr \times State) \cup (BExpr \times State) \cup State \cup Int \cup Bool
e, s \,\Longrightarrow, n 
</math>
</math>


gdzie Instr oznacza zbiór instrukcji (jedna z kategorii syntaktycznych
będą oznaczaczać, że wyrażenie <math>e</math> jest już policzone, a jego wartością jest <math>n</math>.
języka Tiny), Expr zbiór wyrażeń arytmetycznych a BExpr
 
zbiór wyrażeń boolowskich.
Zatem przyjmijmy, że zbiór konfiguracji to
<math>State = Var \to Int</math>.
Konfiguracje końcowe pozostają bez zmian (State).
Tranzycje dla instrukcji pozostają zasadniczo bez zmian, z tym,
że odwołania do funkcji semantycznych dla wyrazżen zastępujemy
przez odpowiednie tranzycje.
Np. dla instrukcji pętli będziemy mieć następujące reguły:


<math>
<math>
\frac{b, s \longrightarrow true  \quad \quad 
( \mathbf{Exp} \times \mathbf{State} ) \, \cup \, \mathbf{Num}
      I; \mathbf{while}\, b \mathbf{do}\, I, s \longrightarrow s'}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \longrightarrow s'}
</math>
</math>
a konfiguracje końcowe to <math>\mathbf{Num}</math>.
{{
uwaga||uwaga1|
Tak naprawdę, 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>.
}}
Najprostsze są tranzycje prowadzące do konfiguracji końcowej:


<math>
<math>
\frac{b, s \longrightarrow false}
n, s \,\Longrightarrow, n
{\mathbf{while}\, b\, \mathbf{do}\, I, s \longrightarrow s}
</math>
</math>


Podobnie dla instrukcji warunkowej.
Symbol <math>n</math> po lewej stronie to wyrażenie składające się ze stałej liczbowej, podczas gdy <math>n</math> po prawej stronie reprezentuje liczbę będącą wartością wyrażenia.
Teraz zajmiemy się tranzycjami dla wyrażeń.
 
Zacznijmy od stalych arytmetycznych:
Zmienna oblicza się do swojej wartości w bieżącym stanie:


<math>
<math>
n, s \longrightarrow n, \quad \quad  \mbox{dla } n \in Int
x, s \,\Longrightarrow, n, s \quad \mbox{ o ile } s(x) = n</math>
</math>


Zauważmy, iż celowo nie odróżniamy liczby <math>n \in Int</math> od
Teraz zajmiemy się dodawaniem <math>e_1 + e_2</math>. Ponieważ semantyka jest w stylu małych kroków, musimy zdecydować się, czy najpierw obliczyć pierwszy (lewy) składnik <math>e_1</math>, czy drugi?
stałej reprezentującej tę liczbę, która może pojawić się
Jeśli wybierzemy lewy (strategia "lewostronna"), otrzymamy regułę:
w wyrażeniach zgodnie z przyjętą przez nas składnią.
Czyli zakładamy, że Int jest podzbiorem zbioru wyrażeń.
W powyższej tranzycji, <math>n</math> po lewej stronie to stała reprezentująca
liczbę, która widnieje po prawej stronie.


Analogiczne tranzycje dla stałych boolowskich to:
<math>
e_1 + e_2, s \,\Longrightarrow, e'_1 + e_2, s
\quad \mbox{ o ile } \quad
e_1, s \,\Longrightarrow, e'_1, s</math>
 
Reguły tej postaci będziemy zapisywać tak:


<math>
<math>
true, s \longrightarrow true
\frac{e_1, s \,\Longrightarrow, e'_1, s}
\quad \quad \quad
    {e_1 + e_2, s \,\Longrightarrow, e'_1 + e_2, s}
false, s \longrightarrow false
</math>
</math>


Analogicznie, czynimy tu założenie, że Bool jest podbiorem
Czyli mały krok w <math>e_1</math> stanowi też mały krok w <math>e_1 + e_2</math>.
wyrażen boolowskich.
Po zakończeniu obliczania <math>e_1</math> przechodzimy do <math>e_2</math>:
 
<math>
\frac{e_2, s \,\Longrightarrow, e'_2, s}
{n + e_2, s \,\Longrightarrow, n + e'_2, s}</math>


Operatory arytmetyczne definiujemy następująco:
A na końcu dodajemy:


<math>
<math>
\frac{e_1, s \longrightarrow n_1  \quad \quad 
n_1 + n_2, s \,\Longrightarrow, n, s \quad \mbox{ o ile } n = n_1 + n_2</math>
        e_2, s \longrightarrow n_2 \quad \quad
 
        n = n_1 + n_2   }
Zwróćmy tutaj uwagę na pewną subtelność, dotyczącą dwóch wystąpień 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>.
{e_1 + e_2,s \longrightarrow n}
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 pisać np. <math>{\mathrm{add}}(e_1, e_2)</math>, a wtedy reguła wyglądałaby tak:
</math>


Czyli aby obliczyć sumę <math>e_1 + e_2</math> w stanie
<math>
<math>s</math>, trzeba najpierw obliczyć <math>e_1</math> i
\mathrm{add}(n_1, n_2), s \,\Longrightarrow, n, s \quad \mbox{ o ile } n = n_1 + n_2</math>
<math>e_2</math> w stanie <math>s</math>, a następnie dodać obliczone wartości.
Zauważmy, że nie specyfikujemy kolejności, w jakiej mają się
obliczać <math>e_1</math> i <math>e_2</math>.
I choć tutaj nie ma to żadnego znaczenia, w przyszłości
będzie inaczej, gdy jezyk będzie umożliwiał efekty uboczne
podzas obliczania wyrażeń.


Podobne reguły można napisać dla pozostałych operacji
Inną możliwą strategią obliczania <math>e_1 + e_2</math> jest strategia "prawostronna", którą otrzymujemy zastępując pierwsze dwie z trzech powyższych reguł przez:
arytmnetycznych, oraz dla spójników logicznych:


<math>
<math>
\frac{b_1, s \longrightarrow l_1  \quad \quad
\frac{e_2, s \,\Longrightarrow, e'_2, s}
      b_2, s \longrightarrow l_2  \quad \quad l = l_1 \land l_2}
    {e_1 + e_2, s \,\Longrightarrow, e_1 + e'_2}
{b_1  \land  b_2, s \longrightarrow l}
\quad \quad
</math>
\frac{e_1, s \,\Longrightarrow, e'_1, s}
    {e_1 + n, s \,\Longrightarrow, e'_1 + n, s}</math>


Oczywiście jeśli <math>b_1</math> oblicza się do false, wartość
Ponadto, jeśli przyjmiemy regułę pierwszą (dla <math>e_1</math>), trzecią 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>:
całego wyrażenia jest false niezależnie od wartości wyrażenia
<math>b_2</math>.
Czyli jeśli zaczniemy od obliczenia <math>b_1</math> i wynikiem będzie
false, to nie ma wogóle potrzeby obliczania <math>b_2</math>.
Oto odpowiednie reguły (nazwijmy je regułami lewo-stronnymi, ponieważ
rozpoczynamy od ''lewego'' koniunktu):


<math>
<math>
\frac{b_1, s \longrightarrow false}
\frac{e_1, s \,\Longrightarrow, e'_1, s}
{b_1 \land b_2, s \longrightarrow false}
    {e_1 + e_2, s \,\Longrightarrow, e'_1 + e_2, s}
\quad \quad
\frac{e_2, s \,\Longrightarrow, e'_2, s}
    {e_1 + e_2, s \,\Longrightarrow, e_1 + e'_2}
\quad \quad
n_1 + n_2, s \,\Longrightarrow, n, s \quad \mbox{ o ile } n = n_1 + n_2</math>
 
Bardziej precyzyjnie mówiąc, małe kroki obliczające obydwa podwyrażenia przeplatają się, i to w dowolny sposób.
Ta dowolność prowadzi do ''niedeterminizmu'', czyli do sytuacji, gdy kolejna (następna) konfiguracja nie jest wyznaczona jednoznacznie.
Jest tak, gdyż możemy mieć do wyboru dwie różne tranzycje


\frac{b_1, s \longrightarrow true  \quad \quad  
<math>
        b_2, s \longrightarrow l}
e_1 + e_2, s \,\Longrightarrow, e'_1 + e_2, s  
{b_1 \land b_2,s \longrightarrow l}
\quad \quad \quad
</math>
e_1 + e_2, s \,\Longrightarrow, e_1 + e'_2, s</math>


Wybraliśmy następującą kolejność obliczania wyrażeń:
Zauważmy natomiast, że kolejność przeplatania się małych kroków obliczających <math>e_1</math> i <math>e_2</math> nie wpływa w tym przypadku na końcową wartość całego wyrażenia.
najpierw b_1, potem b_2.
Pozostawiamy Czytelnikowi napisanie analogicznych reguł dla
kolejności odwrotnej (reguły prawo-stronne).


Rozważmy też następującą kombinację obydwu semantyk
Na koniec reguły dla wyrażenia warunkowego.
(reguły ''równoległe''):


<math>
<math>
\frac{b_1, s \longrightarrow false}
\frac{e_1, s \,\Longrightarrow, e'_1, s}
{b_1 \land b_2,s \longrightarrow false}
    {\mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s \,\Longrightarrow, \mathbf{if}\, e'_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s}
\quad \quad \quad
\frac{b_2, s \longrightarrow false}
{b_1 \land b_2,s \longrightarrow false}
</math>
</math>


Czyli jeśli którekolwiek z podwyrażeń daje wynik false,
<math>
to taki wynik zyskuje całe wyrażenie.
\mathbf{if}\, n \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s \,\Longrightarrow, e_2, s \quad \mbox{ o ile } n \neq 0
Dodatkowo potrzebujemy jeszcze reguły:
</math>


<math>
<math>
\frac{b_1, s \longrightarrow true  \quad \quad
\mathbf{if}\, n \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s \,\Longrightarrow, e_3, s \quad \mbox{ o ile } n = 0
b_2, s \longrightarrow true}
{b_1 \land b_2,s \longrightarrow true}
</math>
</math>


Zauważmy, że powyższych reguł nie da sie zaimplementować
</div></div>
sekwencyjnie: nie wiadomo czy najpierw obliczać
<math>b_1</math> czy <math>b_2</math>.
Reguły te odpowiadają raczej strategii ,,równoległej'':
obliczaj ,,jednocześnie'' b_1 i b_2 do momentu, gdy
jedno z nich obliczy się do wynikiem false, albo aż obydwa się zakończą
z wynikiem true.


W naszym prostym języku wszystkie cztery warianty
są równoważne. Różnice pomiędzy nimi zobaczymy jednak już w
następnym zadaniu, w którym pojawi się prosta
odmiana efektów ubocznych (błąd wykonania).




Reguły dla pozostałych spójników logicznych oraz dla
{{cwiczenie|2|cw2|
negacji pozostawiamy jako ćwiczenie.
}}
A teraz małe kroki.


===== Strukturalna semantyka operacyjna (małe kroki)  =====
Rozszerzmy język wyrażeń z poprzedniego zadania o jedną konstrukcję


Chcemy, aby tranzycje dla wyrażeń były postaci:
<math>
<math>
e, s \longrightarrow e', s
e \,  ::=  \,\, 
        \ldots  \,\,|\,\,
        \mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2
</math>
</math>
i podobnie dla wyrażeń boolowskich:
 
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 wiązania identyfikatorów w naszym języku.
Deklaracja <math>x = e_1</math> wprowadza nową zmienną <math>x</math> oraz przypisuje jej wartość.
Wartość wyrażenia <math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math> obliczamy następująco: najpierw oblicza się wartość <math>e_1</math>, podstawia ją <font color=red>za</font> zmienną <math>x</math>, a następnie oblicza wyrażenie <math>e_2</math>.
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, 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 <math>e_2</math> występuje podwyrażenie <math>\mathbf{let}\, x = e \,\mathbf{in}\, e'</math>, to
deklaracja <math>x = e</math> "przesłania" deklarację <math>x = e_1</math> 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 uważane za niepoprawne.
 
 
 
{{przyklad|||
 
<math>
<math>
b, s \longrightarrow b', s
\mathbf{let}\, x = 0 \,\mathbf{in}\, \mathbf{let}\, y = 7 \,\mathbf{in}\, \mathbf{let}\, x = y+3 \,\mathbf{in}\, x+x+y
\quad \quad \mapsto \quad \quad \mbox{wynik} = 24
</math>
 
<math>
\mathbf{let}\, y = 5 \,\mathbf{in}\, \mathbf{let}\, x = (\, \mathbf{let}\, y = 3 \,\mathbf{in}\, y+y \,) \,\mathbf{in}\, x+y
\quad \quad \mapsto \quad \quad \mbox{wynik} = 11
</math>
</math>
gdzie <math>s \in State</math>.
Przyjmijmy na razie takie same konfiguracje i konfiguracje końcowe
jak dla semantyki naturalnej.


Zacznijmy od wyrażeń boolowskich.
<math>
\mathbf{let}\, z = 5 \,\mathbf{in}\, x+z \quad \quad \mapsto \quad \quad \mbox{ brak wyniku, odwołanie do niezainicjowanej zmiennej } x
</math>


<math>
<math>
true, s \Longrightarrow true
\mathbf{let}\, x = 1 \,\mathbf{in}\, \mathbf{let}\, x = x+x \,\mathbf{in}\, x+x
\quad \quad
\quad \quad \mapsto \quad \quad \mbox{wynik} = 4
false, s \Longrightarrow false
</math>
</math>


Przejdźmy do spójników logicznych, powiedzmy <math>b_1 \land b_2</math>.
<div class="mw-collapsible mw-made=collapsible mw-collapsed">
Ponieważ opisujemy teraz pojedyncze (małe) kroki składające się na
<span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie</span>
wykonanie programu, musimy podać w jakiej kolejności będą się
<div class="mw-collapsible-content" style="display:none">
obliczać <math>b_1</math> i <math>b_2</math>. Zacznijmy od strategii lewostronnej:
 
Podobnie jak poprzednio, stan powinien opisywać wartości przypisane zmiennym.
Tym razem jednak uwzględnimy niezainicjowane zmienne, czyli zmienne bez żadnej wartości.
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:
<math>
\mathbf{State} = \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Num}
</math>.
Naturalnym stanem początkowym jest stan "pusty", tzn. 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:
 
<math>
e, \emptyset \,\Longrightarrow^{*}\, n</math>
 
Będziemy potrzebowac tranzycji dwóch postaci, podobnie jak poprzednio, ale pierwsza postać będzie nieco ogólniejsza:


<math>
<math>
\frac{b_1, s \Longrightarrow b'_1, s}
e, s \,\Longrightarrow, e', s'</math>
{b_1 \land b_2, s \Longrightarrow b'_1 \land b_2, s}
 
\quad \quad
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> wyewoluowało do <math>e'</math>, a nowym stanem jest <math>s'</math>.
\frac{b_2, s \Longrightarrow b'_2, s}
Stan może się teraz zmienić na skutek deklaracji zmiennych.
{l_1 \land b_2, s \Longrightarrow l_1 \land b_2, s}
 
\quad \quad
Spróbujmy rozszerzyć semantykę z poprzedniego zadania.
l_1 \land l_2 \Longrightarrow l, 
Ponieważ stan jest funkcją częściową, musimy zmienić niektóre reguły, np.
\mbox{ o ile } l = l_1 \land l_2
 
<math>
x, s \,\Longrightarrow, n, s \quad \mbox{ o ile } s(x) \mbox{ jest określone i } s(x) = n
</math>
</math>


Podobnie jak poprzednio, możemy zaniechać obliczania
Następnie dodajemy reguły dla wyrażenia <math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math>.
<math>b_2</math> jeśli <math>b_1</math> oblicza się do false.
Gdy <math>e_1</math> jest już obliczone, wystarczy reguła:
Oto odpowiednio zmodyfikowane reguły:
 
<math>
\mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow, e_2, s[x \mapsto n]</math>
 
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 pozostawiając niezmienione wartości dla pozostałych zmiennych.
Formalnie


<math>
<math>
\frac{b_1, s \Longrightarrow b'_1, s}
s[x \mapsto n](y) =
{b_1 \land b_2, s \Longrightarrow b'_1 \land b_2, s}
\begin{cases}
\quad \quad
n    & y = x \\
false \land b_2, s \Longrightarrow false
s(y) & y \neq x
\quad \quad
\end{cases}
true  \land b_2, s \Longrightarrow b_2, s
</math>
</math>


Analogicznie reguły prawostronne to:
W szczególności 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 określone.
 
Natomiast aby obliczyc <math>e_1</math>, potrzebujemy reguły:


<math>
<math>
\frac{b_2, s \Longrightarrow b'_2, s}
\frac{e_1, s \,\Longrightarrow, e'_1, s'}
{b_1 \land b_2, s \Longrightarrow b_1 \land b'_2, s}
{\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\Longrightarrow, \mathbf{let}\, x = e'_1 \,\mathbf{in}\, e_2, s'}
\quad \quad
b_1 \land false, s \Longrightarrow false
\quad \quad
b_1 \land true, s \Longrightarrow b_1, s
</math>
</math>


Reguły ''równoległe'' otrzymujemy jako sumę reguł lewo- i
Zwróćmy uwagę, że stan <math>s'</math> może być różny od <math>s</math>, np. dlatego, że wewnątrz <math>e_1</math> znajduje się podwyrażenie <math>\mathbf{let}\, y = \ldots</math>.
prawostronnych (w sumie 6 reguł). Zauważmy, że obliczanie
 
wyrażeń <math>b_1</math> i <math>b_2</math> odbywa się teraz
'''Pytanie:''' czy taka semantyka jest poprawna?
w twz. ''przeplocie'': Pojedynczy krok polega na wykonaniu
jednego kroku obliczenia <math>b_1</math> albo jednego kroku
obliczenia <math>b_2</math>.
Zwróćmy też uwagę, że po raz pierwszy nasze tranzycje nie posiadają
własności ''determinizmu'': wyrażenie <math>b_1 \land b_2</math>
może wyewoluować w pojedyńczym kroku albo do
<math>b'_1 \land b_2</math> albo do <math>b_1 \land b'_2</math>.
Na szczęście, końcowy wynik, do jakiego oblicza się wyrażenie
jest zawsze taki sam, niezależnie od przeplotu.


Oto reguła dla negacji:
Niestety nie, gdyż nie uwzględniamy ograniczonego zasięgu zmiennej.
Rzućmy okiem na przykład:


<math>
<math>
\neg true, s \Longrightarrow false, s
\mathbf{let}\, x = (\mathbf{let}\, z = 4 \,\mathbf{in}\, z+z+z) \,\mathbf{in}\, z
\quad \quad \quad
\neg false, s \Longrightarrow true, s
\quad \quad \quad
\frac{b, s \Longrightarrow b', s}
    {\neg b, s \Longrightarrow \neg b', s}
</math>
</math>


Reguły dla <math>e_1 \leq e_2</math> są następujące:
Według naszych intencji to wyrażenie nie ma wartości, gdyż ostatnie odwołanie do <math>z</math> jest błędne.
Natomiast według powyższych reguł mamy


<math>
<math>
\frac{e_1, s \Longrightarrow e'_1, s}
\mathbf{let}\, x = (\mathbf{let}\, z = 4 \,\mathbf{in}\, z+z+z) \,\mathbf{in}\, z, \emptyset \,\Longrightarrow,
{e_1 \leq e_2, s \Longrightarrow e'_1 \leq e_2, s}
\mathbf{let}\, x = z+z+z \,\mathbf{in}\, z, \emptyset[z \mapsto 4] \,\Longrightarrow, \quad \ldots \quad \,\Longrightarrow,
\mathbf{let}\, x = 12 \,\mathbf{in}\, z, \emptyset[z \mapsto 4] \,\Longrightarrow,
12, \emptyset[z \mapsto 4] \,\Longrightarrow,
12 !
</math>


\frac{e_2, s \Longrightarrow e'_2, s}
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" przywrócić zmiennej <math>z</math> poprzednią wartość (a właściwie brak wartości w przykładzie powyżej).
{e_1 \leq e_2, s \Longrightarrow e_1 \leq e'_2, s}
Przedyskutujmy kilka wariantów.


n_1 \leq n_2, s \Longrightarrow true, s  \quad \mbox{ o ile }
<br>
n_1 \leq n_2
'''Wariant 1'''
<br>


n_1 \leq n_2, s \Longrightarrow false, s  \quad  \mbox{ o ile }
Wygodne i eleganckie rozwiązanie tego problemu jest możliwe, jeśli rozszerzymy składnię naszego języka.
n_1 > n_2
Intuicyjnie, reguła
</math>


Reguły powyższe zależą od semantyki wyrażen arytmetycznych.
<math>
Zauważmy, że ponownie pozostawiliśmy dowolność jeśli chodzi o
\mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow, e_2, s[x \mapsto n]</math>
kolejność obliczania wyrażeń arytmetycznych e_1 i e_2.


Rozważmy teraz instrukcję warunkową i instrukcję pętli.
powinna zostać zastąpiona przez
Najpierw obliczamy wartość dozoru:


<math>
<math>
\frac{b, s \Longrightarrow b', s}
\mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow\, e_2 \,\mathbf{then}\, \mbox{przywróć wartość zmiennej x}, s[x \mapsto n]</math>
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow  
\mathbf{if}\, b'\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s}
\quad \quad
\frac{b, s \Longrightarrow b', s}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow \mathbf{while}\, b'\, \mathbf{do}\, I, s}
</math>


a gdy dozór jest już obliczony, podejmujemy decyzję.
czyli potrzebujemy konstrukcji składniowej, która polega na obliczeniu wyrażenia <math>e_2</math>, a następnie na przypisaniu zmiennej <math>x</math> danej wartości.
W przypadku instrukcji warunkowej reguły są oczywiste:
Rozszerzmy zatem składnię następujaco:


<math>
<math>
\mathbf{if}\, true\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_1, s
e \,  ::=  \,\,
\quad \quad \quad
        \ldots  \,\,|\,\,
\mathbf{if}\, false\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \Longrightarrow I_2, s
        e \,\mathbf{then}\, x := n</math>
</math>


Gorzej jest w przypadku instukcji pętli. Reguła mogłaby wyglądać tak:
Wyrażenie <math>e \,\mathbf{then}\, x:= n</math> jest w pewnym sensie dualne do <math>\mathbf{let}\, x = n \,\mathbf{in}\, e</math>, gdyż jedyna (choć niewątpliwie istotna) różnica między nimi to kolejność obliczenia <math>e</math> i przypisania wartości na zmienną <math>x</math>.  
Oto nowa reguła


<math>
<math>
\mathbf{while}\, true\, \mathbf{do}\, I, s \Longrightarrow  
\mathbf{let}\, x = n \,\mathbf{in}\, e_2, s \,\Longrightarrow, e_2 \,\mathbf{then}\, x := n', s[x \mapsto n] \quad
I;\, \mathbf{while}\, ?\, \mathbf{do}\, I, s
\mbox{ o ile } s(x) = n'</math>
</math>


ale nie wiemy już, jaki był dozór pętli (widzimy tylko wynik
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.
obliczenia tego dozoru w stanie s, true).
Najprostszym sposobem rozwiązania tej trudności jest rozszerzenie konstrukcji <math>e \,\mathbf{then}\, x := n</math>:
Możemy odwołać się do tranzycji dużych kroków:


<math>
<math>
\frac{b, s \longrightarrow true}
e \,  ::=  \,\
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow
        \ldots  \,\,|\,\,
I;\, \mathbf{while}\, b\, \mathbf{do}\, I, s}
        e \,\mathbf{then}\, x := n  \,\,|\,\,
\quad \quad \quad
        e \,\mathbf{then}\, x := \bot
\frac{b, s \longrightarrow false}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \Longrightarrow s}
</math>
</math>


Takie rozwiązanie nie jest zatem ''czystą'' semantyką
gdzie symbol <math>\bot</math> oznacza brak wartości.
małych kroków.
Dodajemy również regułę:
Istnieją inne możliwe rozwiązania, w stylu małych kroków,
 
których znalezienie pozostawiamy dociekliwemu czytelnikowi.
<math>
\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 nieokreślone}</math>


Na koniec podajemy reguły dla operacji arytmetycznych, na przykładzie
Rozwiązanie to jest odrobinę nieeleganckie, gdyż prawie identyczne reguły musimy napisać dwukrotnie.
dodawania.
Widać to np. w poniższych regułach, "scalających" semantykę dla <math>e \,\mathbf{then}\, x := n</math> z semantyką pozostałych wyrażeń:
Przyjmijmy, dla przykładu, strategię lewostronną:  


<math>
<math>
\frac{e_1, s \Longrightarrow e'_1, s}
\frac{e, s \,\Longrightarrow, e', s'}
{e_1 + e_2, s \Longrightarrow e'_1 + e_2, s}  
{e \,\mathbf{then}\, x := n, s \,\Longrightarrow, e' \,\mathbf{then}\, x:= n, s'}
</math>


\frac{e_2, s \Longrightarrow e'_2, s}
<math>
{n + e_2, s \Longrightarrow n + e'_2, s}
n' \,\mathbf{then}\, x := n, s \,\Longrightarrow, n', s[x \mapsto n]
</math>


n_1 + n_2, s \Longrightarrow n, s \quad \mbox{ o ile }
<math>
n = n_1 + n_2
n' \,\mathbf{then}\, x := \bot, s \,\Longrightarrow, n', s' \quad \mbox{ o ile } s(x)
\mbox{ jest określone i } s' = s \setminus \{ (x, s(x)) \}
</math>
</math>


Niektóre konfiguracje nie były wogóle przez nas używane. Które?
<br>
'''Wariant 2'''
<br>
 
Zanim przejdziemy do kolejnego wariantu, zastanówmy się, czy istnieje inny sposób rozwiązania trudności związanej z <math>n = \bot</math>, który pozwalałby uniknąć wprowadzania dodatkowej konstrukcji
<math>e \,\mathbf{then}\, x := \bot</math>.
Pomysł może polegać na rozszerzeniu zbioru <math>\mathbf{Num}</math> o dodatkowy element <math>\bot</math>:


==== Zadanie 2 ====
Rozważ dodatkowo operację dzielenia:
<math>
<math>
e \, ::= \,\,   \ldots  \,\,|\,\,
n \, ::= \,\, \bot \,\,|\,\, 0 \,\,|\,\, 1 \,\,|\,\, \ldots
        e_1 / e_2
</math>
</math>
i rozszerz semantyki z poprzedniego zadania.
Dzielenie przez zero jest błądem i  kończy natychmiast wykonanie
programu.
Oprócz stanu wynikiem programu powinna byc informacja o błędzie,
jeśli błąd wystąpił.


==== Rozwiązanie (szkic) ====
Wtedy nie musimy pisać dwóch bardzo podobnych wariantów reguł.
Dodatkowo, w tym rozwiązaniu warto poczynić umowę, że <math>s(x) = \bot</math> reprezentuje brak wartości zmiennej <math>x</math>.
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.
Pewnym mankamentem jest to, że teraz <math>n = \bot</math> może pojawiać się w wyrażeniach podobnie jak stałe.
Tym niemniej nie musimy adaptować reguł dla stałych tak, aby radziły one sobie z <math>n = \bot</math>, ponieważ wyrażenia zawierające <math>\bot</math> możemy również uważać za roszerzenie składni.


Dopóki nie wystąpi błąd dzielenia przez zero, semantyka programu
Jeśli jednak dopuścimy symbol <math>\bot</math> w wyrażeniach, to możemy elegancko wybrnąć z sytuacji, rozszerzając operacje arytmetyczne na zbiór <math>\mathbf{Num} \cup \{ \bot \}</math> tak, aby zachowywały one nieokreśloność:
powinna być identyczna jak w poprzednim zadaniu. Zatem pozostawiamy
wszystkie reguły z poprzedniego zadania, tak w semantyce naturalnej
jak i w semantyce małych kroków.
Dodatkowo potrzebujemy reguł, które opiszą
** kiedy powstaje błąd oraz
** jak zachowuje się program po wystąpieniu błędu


Zaczynamy od pierwszego punktu.
<math>
W tym celu dodajemy do konfiguracji jedną konfigurację końcową
n + \bot = \bot + n = \bot</math>
<math>Blad</math>.
 
Reguła opisująca powstanie błędu może wyglądać np. tak
Trzeba jednak w takim razie zadbać o to, aby wyrażenie <math>\mathbf{let}\, x = e_1 \,\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>.
w semantyce naturalnej:
,
<br>
'''Wariant 3'''
<br>
 
Zrewidujmy teraz podstawowe założenia, które dotychczas poczyniliśmy.
Jednym z nich było przyjęcie ogólnej postaci tranzycji:


<math>
<math>
\frac{e_2, s \longrightarrow 0}
e, s \,\Longrightarrow, e', s'
{e_1 / e_2, s \longrightarrow Blad}
</math>
</math>


a tak w semantyce małych kroków:
pozwalającej na zmianę stanu podczas obliczania wyrażenia.
Czy faktycznie był to dobry pomysł? Czy moglibyśmy poradzić sobie przy pomocy tranzycji postaci


<math>
<math>
e_1 / 0, s \Longrightarrow Blad
e, s \,\Longrightarrow, e', s ?
</math>
</math>


Pomijamy tutaj reguły dla przypadku, gdy <math>e_2</math>
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>:
oblicza się do wartości różnej od zera.
Ponadto dla czytelności przyjęliśmy, że wynikiem tranzycji jest
wyłącznie informacja o błędzie, a stan jest zapominany.
Bez trudu możnaby wszystkie reguły (zarówno te powyżej jak i te
poniżej) zmodyfikować tak, by wraz z
informacją o błędzie zwracany był też stan, w którym błąd się
pojawił. Np. ostatnia reguła wyglądałaby następująco:


<math>
<math>
e_1 / 0, s \Longrightarrow Blad, s
\frac{e_1, s \,\Longrightarrow, e'_1, s}
{\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\Longrightarrow, \mathbf{let}\, x = e'_1 \,\mathbf{in}\, e_2, s}
</math>
</math>


I zamiast pojedyńczej konfiguracji końcowej <math>Blad</math>, potrzebowalibyśmy
Dotychczas nie ma problemu: podwyrażenie <math>e_1</math> jest prawidłowo obliczane w stanie <math>s</math>. Trudność pojawi się, gdy
oczywiście całego zbioru <math>\{Blad\} \times State</math>.
zakończymy obliczanie <math>e_1</math> i przejdziemy do <math>e_2</math>.
Oto możliwa reguła:
 
<math>
\frac{e, s[x \mapsto n] \,\Longrightarrow, e', s[x \mapsto n] }
    {\mathbf{let}\, x = n \,\mathbf{in}\, e, s \,\Longrightarrow, \mathbf{let}\, x = n \,\mathbf{in}\, e', s}</math>


Przejdźmy teraz do drugiego punktu. Potrzebujemy dodatkowych reguł,
Okazuje się, że wszystko jest w porządku. Wyrażenie <math>e</math> obliczamy w prawidłowym stanie, tzn. z wartością <math>n</math> przypisaną zmiennej <math>x</math>.
które zagwarantują, że błąd, raz pojawiwszy się, propaguje się przez
Mały krok w <math>e</math> daje przyczynek do małego kroku w całym wyrażeniu, a przy tym stan pozostaje niezmieniony.
wszystkie konstrukcje składniowe, a normalne obliczenie wyrażenia
Przy tym wogóle nie potrzebujemy przywracać poprzedniej wartości zmiennej <math>x</math>, ponieważ <math>x</math> zyskuje nową wartość "tylko" na potrzeby obliczania podwyrażenia <math>e</math>!
jest wstrzymame.
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>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>.
Zacznijmy od sementyki naturalnej:
Na końcu musimy oczywiście pozbyć się tej deklaracji za pomocą następującej tranzycji:


<math>
<math>
\frac{e_1, s \longrightarrow Blad}
\mathbf{let}\, x = n \,\mathbf{in}\, n', s \,\Longrightarrow, n', s
{e_1 \leq e_2, s \longrightarrow Blad}
\quad \quad \quad
\frac{e_2, s \longrightarrow Blad}
{e_1 \leq e_2, s \longrightarrow Blad}
</math>
</math>


Następnie, błąd w wyrażeniu powinien wstrzymać normalne wykonanie
Podsumujmy. Okazuje się, że rozwiązanie nie było wcale łatwe, 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 (duże kroki).
instrukcji:
W wariancie 1 i 2 wprowadziliśmy do języka dodatkowe elementy, tak by łatwiej było pisać reguły. W przyszłości będziemy czasem stosować takie podejście.
Niekiedy jednak rozszerzanie języka będzie zabronione.
 
</div></div>
 
== Zadania domowe ==
 
 
{{cwiczenie|1|cw1.dom|
 
Zapisz wariant 2 semantyki z poprzedniego zadania.
}}
 
 
{{cwiczenie|2|cw2.dom|
 
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 <math>e</math> w stanie <math>s</math> jest niemożliwe bo wystąpił błąd, to
 
<math>
e, s \,\Longrightarrow^{*}\, \mathtt{Blad}</math>
}}
 
 
{{cwiczenie|3|cw3.dom|
 
Rozważ rozszerzenie języka wyrażeń o wyrażenia boolowskie:


<math>
<math>
\frac{b \longrightarrow Blad}
n \, ::= \,\, 0 \,\,|\,\, 1 \,\,|\,\, \ldots
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \longrightarrow Blad}
\quad \quad \quad
\frac{b \longrightarrow Blad}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \longrightarrow Blad}
</math>
</math>


I wreszcie błąd powinien propagować się do kolejnych instrukcji:
<math>
x \, ::= \,\, \ldots \, (identyfikatory) \, \ldots
</math>


<math>
<math>
\frac{I_1, s \longrightarrow Blad}
b \, ::= \,\,  
{I_1;\, I_2, s \longrightarrow Blad}
        \mathbf{true}   \,\,|\,\,
\quad \quad \quad
        \mathbf{false} \,\,|\,\,
\frac{I_1, s \longrightarrow Blad}
        e_1 \leq e_2  \,\,|\,\,
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \longrightarrow Blad}
        \neg b  \,\,|\,\,
\quad \quad \quad
        b_1  \land  b_2
\frac{I_2, s \longrightarrow Blad}
{\mathbf{if}\, b\, \mathbf{then}\, I_1\, \mathbf{else}\, I_2, s \longrightarrow Blad}
\quad \quad \quad
\frac{I, s \longrightarrow Blad}
{\mathbf{while}\, b\, \mathbf{do}\, I, s \longrightarrow Blad}
</math>
</math>


I tak dalej.
<math>
e \,  ::=  \,\, 
        n  \,\,|\,\,
        x  \,\,|\,\,
        e_1 + e_2  \,\,|\,\,
        \mathbf{if}\, b \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3  \,\,|\,\,
        \mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2


Pytanie dla Czytelnika: jak napisać tę semantykę w podejściu mało-krokowym?
</math>


(Powyżej traktowaliśmy tranzycję
Zaproponuj semantykę małych kroków dla tego języka.
<math> I, s \longrightarrow Blad </math> jak ''duży krok''.
Rozważ różne strategie obliczania wyrażeń boolowskich, oraz podejście leniwe.
Ale tak naprawdę pojawienie się błędu powinno spowodować
Na przykład w strategii lewostronnej dla <math>b_1 \land b_2</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>.
''natychmiastowe'' zatrzymanie programu, więc może tranzycję taką
}}
można również traktować jak ''mały krok'',  
<math> I, s \Longrightarrow Blad </math>?)

Aktualna wersja na dzień 21:29, 11 wrz 2023

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ą:

n::=0|1|

x::=(identyfikatory)

e::=n|x|e1+e2|𝐢𝐟e1𝐭𝐡𝐞𝐧e2𝐞𝐥𝐬𝐞e3

Wynikiem wyrażenienia warunkowego 𝐢𝐟e1𝐭𝐡𝐞𝐧e2𝐞𝐥𝐬𝐞e3 jest wartość wyrażenia e2, o ile wyrażenie e1 oblicza się do wartości różnej od zera; w przeciwnym przypadku wynikiem jest wartość wyrażenia e3.

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ę

e::=|𝐥𝐞𝐭x=e1𝐢𝐧e2

Wyrażenie 𝐥𝐞𝐭x=e1𝐢𝐧e2 zawiera w sobie deklarację x=e1, która stanowi mechanizm wiązania identyfikatorów w naszym języku. Deklaracja x=e1 wprowadza nową zmienną x oraz przypisuje jej wartość. Wartość wyrażenia 𝐥𝐞𝐭x=e1𝐢𝐧e2 obliczamy następująco: najpierw oblicza się wartość e1, podstawia ją za zmienną x, a następnie oblicza wyrażenie e2. Zakresem zmiennej x jest wyrażenie e2, czyli wewnątrz e2 można odwoływać się (wielokrotnie) do zmiennej x; 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 e2 występuje podwyrażenie 𝐥𝐞𝐭x=e𝐢𝐧e, to deklaracja x=e "przesłania" deklarację x=e1 w wyrażeniu e.

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.


{{przyklad|||

𝐥𝐞𝐭x=0𝐢𝐧𝐥𝐞𝐭y=7𝐢𝐧𝐥𝐞𝐭x=y+3𝐢𝐧x+x+ywynik=24

𝐥𝐞𝐭y=5𝐢𝐧𝐥𝐞𝐭x=(𝐥𝐞𝐭y=3𝐢𝐧y+y)𝐢𝐧x+ywynik=11

𝐥𝐞𝐭z=5𝐢𝐧x+z brak wyniku, odwołanie do niezainicjowanej zmiennej x

𝐥𝐞𝐭x=1𝐢𝐧𝐥𝐞𝐭x=x+x𝐢𝐧x+xwynik=4

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 e w stanie s jest niemożliwe bo wystąpił błąd, to

e,s*Blad


Ćwiczenie 3

Rozważ rozszerzenie języka wyrażeń o wyrażenia boolowskie:

n::=0|1|

x::=(identyfikatory)

b::=𝐭𝐫𝐮𝐞|𝐟𝐚𝐥𝐬𝐞|e1e2|¬b|b1b2

e::=n|x|e1+e2|𝐢𝐟b𝐭𝐡𝐞𝐧e2𝐞𝐥𝐬𝐞e3|𝐥𝐞𝐭x=e1𝐢𝐧e2

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 b1b2, gdy b1 zostało obliczone do 𝐟𝐚𝐥𝐬𝐞, w podejściu leniwym nie ma wogóle potrzeby obliczania b2.