Semantyka i weryfikacja programów/Ćwiczenia 4: 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 16 wersji utworzonych przez 5 użytkowników)
Linia 1: Linia 1:


== Zawartość ==
== Zawartość ==


Napiszemy semantykę naturalną języka wyrażeń (z <math> \mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2 </math>),
Napiszemy semantykę naturalną języka wyrażeń, rozważymy strategię gorliwą (jak na wcześniejszych zajęciach, w semantyce małych kroków) i leniwą.
rozważymy strategię gorliwą (jak na wcześniejszych zajęciach) i
leniwą.
Rozważymy i statyczne i dynamiczne wiązanie identyfikatorów.
Rozważymy i statyczne i dynamiczne wiązanie identyfikatorów.
Następnie rozszerzymy ten język o lambda-abstrakcję i aplikację,
Następnie rozszerzymy ten język o lambda-abstrakcję i aplikację, otrzymując prosty język funkcyjny.
otrzymując prosty język funkcyjny.
 


== Różne semantyki naturalne wyrażeń ==


== Różne semantyki wyrażeń ==


{{cwiczenie|1 (semantyka gorliwa)|cw1|


==== Zadanie 1 (semantyka gorliwa) ====


Napisz semantykę dużych kroków
Napisz semantykę dużych kroków dla języka wyrażeń, którego semantykę małokrokową napisaliśmy na jednych z poprzednich ćwiczeń:
dla języka wyrażeń, którego semantykę mało-krokową
napisaliśmy na jednych z poprzednich ćwiczeń:


<math>
<math>
Linia 36: Linia 31:
         \mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2
         \mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2
</math>
</math>
}}


==== Rozwiązanie ====
<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">


Przypomnijmy, że zbiór stanów to
Przypomnijmy, że zbiór stanów to
Linia 44: Linia 44:
</math>
</math>


Nasze tranzycje będą postaci <math> e, s \,\longrightarrow\, n </math>, gdzie
Nasze tranzycje będą postaci <math>e, s \,\longrightarrow\, n</math>, gdzie <math>e \in \mathbf{Exp}, s \in \mathbf{State}, n \in \mathbf{Num}</math>.
<math> e \in \mathbf{Exp}, s \in \mathbf{State}, n \in \mathbf{Num} </math>.
Oto reguły semantyki naturalnej.
Oto reguły semantyki naturalnej.


<math>
<math>
n, s \,\longrightarrow\, n
n, s \,\longrightarrow\, n
</math>
</math>


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


<math>
<math>
\frac{e_1, s \,\longrightarrow\, n_1 \quad e_2, s \,\longrightarrow\, n_2}
\frac{e_1, s \,\longrightarrow\, n_1 \quad e_2, s \,\longrightarrow\, n_2}
     {e_1 + e_2, s \,\longrightarrow\, n} \quad \mbox{ o ile } n = n_1 + n_2
     {e_1 + e_2, s \,\longrightarrow\, n} \quad \mbox{ o ile } n = n_1 + n_2
</math>
</math>


<math>
<math>
\frac{e_1, s \,\longrightarrow\, n_1 \quad e_2, s[x \mapsto n] \,\longrightarrow\, n_2}
\frac{e_1, s \,\longrightarrow\, n_1 \quad e_2, s[x \mapsto n_1] \,\longrightarrow\, n_2}
     {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, n_2}
     {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, n_2}
</math>
</math>


<math>
<math>
\frac{e_1, s \,\longrightarrow\, n_1 \quad n_1 \neq 0 \quad e_2, s \,\longrightarrow\, n_2}
\frac{e_1, s \,\longrightarrow\, n_1 \quad n_1 \neq 0 \quad e_2, s \,\longrightarrow\, n_2}
     {\mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s \,\longrightarrow\, n_2}
     {\mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s \,\longrightarrow\, n_2}
</math>
</math>


<math>
<math>
\frac{e_1, s \,\longrightarrow\, n_1 \quad n_1 = 0 \quad e_3, s \,\longrightarrow\, n_3}
\frac{e_1, s \,\longrightarrow\, n_1 \quad n_1 = 0 \quad e_3, s \,\longrightarrow\, n_3}
     {\mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s \,\longrightarrow\, n_3}
     {\mathbf{if}\, e_1 \,\mathbf{then}\, e_2 \,\mathbf{else}\, e_3, s \,\longrightarrow\, n_3}
</math>
</math>


Zauważmy, że opisanie zasięgu deklaracji <math> x = e_1 </math> nie predstawia
Zwróćmy uwagę na fakt, że prawidłowe odwzorowanie zasięgu deklaracji <math>x = e_1</math> nie predstawia w semantyce naturalnej żadnych trudności, w przeciwieństwie do semantyki małych kroków.
w semantyce naturalnej żadnych trudności, w przeciwieństwie do  
 
semantyki małych kroków.
</div></div>




==== Zadanie 2 (semantyka leniwa) ====


Zmodyfikuj semantykę z poprzedniego zadania, aby uzyskać  
{{cwiczenie|2 (semantyka leniwa)|cw2|
''leniwą'' ewaluację wyrażeń, zgodnie z dyrektywą: nie obliczaj
 
wyrażenia o ile jego wynik nie jest potrzebny
 
(albo: obliczaj wartość wyrażenia dopiero wtedy, gdy jego wynik jest
Zmodyfikuj semantykę z poprzedniego zadania, aby uzyskać ''leniwą'' ewaluację wyrażeń, zgodnie z dyrektywą: nie obliczaj wyrażenia, o ile jego wynik nie jest potrzebny (albo: obliczaj wartość wyrażenia dopiero wtedy, gdy jego wynik jest naprawdę potrzebny).  
naprawdę potrzebny). Spójrzmy na przykład:
Spójrzmy na przykład:


<math>
<math>
Linia 93: Linia 91:
</math>
</math>


Według semantyki z poprzedniego zadania wyrażnie to nie ma wartości,
Według semantyki z poprzedniego zadania wyrażnie to nie ma wartości, bo w deklaracji <math>y = y+y</math> jest odwołanie do niezainicjowanej zmiennej.
bo w deklaracji <math> y = y+y </math> jest odwołanie do niezainicjowanej
Natomiast w semantyce leniwej wyrażenie to obliczy się do wartości <math>14</math>, gdyż wyrażenie <math>y+y</math> nie będzie wogóle obliczane.
zmiennej.
Będzie tak dlatego, że w wyrażeniu <math>x+x</math> nie ma odwołań do zmiennej <math>y</math>.
Natomiast w semantyce leniwej wyrażenie to obliczy się do wartości
}}
<math> 14 </math>, gdyż wyrażenie <math> y+y </math> nie będzie wogóle obliczane.
Będzie tak dlatego, że w wyrażeniu <math> x+x </math> nie ma odwołań do
zmiennej <math> y </math>.




==== Rozwiązanie ====
 
<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">


Semantyka ''leniwa'' będzie bardzo podobna do tej z poprzedniego zadania.  
Semantyka ''leniwa'' będzie bardzo podobna do tej z poprzedniego zadania.  
Zasadnicza różnica dotyczy informacji przechowywanej w stanie.
Zasadnicza różnica dotyczy informacji przechowywanej w stanie.
Dotychczas <math> s(x) \in \mathbf{Num} </math>, gdyż podwyrażenie <math> e </math> w
Dotychczas <math>s(x)</math> nalażał do zbioru <math>\in \mathbf{Num}</math>, gdyż podwyrażenie <math>e</math> w <math>\mathbf{let}\, x = e \,\mathbf{in}\, \ldots</math> obliczało sie natychmiast.
<math> \mathbf{let}\, x = e \,\mathbf{in}\, \ldots </math> obliczało sie natychmiast.
Jeśli chcemy opóżnic obliczenie tego podwyrażenia, to w <math>s(x)</math> powinniśmy zapamiętać całe (nieobliczone) wyrażenie <math>e</math> wraz ze stanem bieżącym.
Jeśli chcemy opóżnic obliczenie tego podwyrażenia, to w
<math> s(x) </math> powinniśmy zapamiętać całe (nieobliczone) wyrażenie <math> e </math>
wraz ze stanem bieżącym.
Czyli
Czyli


<math>
<math>
\mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp} \times \mathbf{State}.
\mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp} \times \mathbf{State}</math>
</math>
 
Np. odpowiednia reguła dla wyrażenia <math>\mathbf{let}\ </math>, w semantyce małych kroków mogłaby wyglądać następująco:


Odpowiednia reguła dla wyrażenia <math> \mathbf{let}\, </math> to
<math>
\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\Longrightarrow, e_2, s[x \mapsto (e_1, s)]</math>


<math>
Czyli stan zawiera, dla każdej zmiennej, parę (wyrażenie definiujące, stan w momencie deklaracji).
\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\Longrightarrow\, e_2, s[x \mapsto (e_1, s)].
</math>


Uważnego czytelnika zapewne zaniepokoił fakt, że <math> \mathbf{State} </math>
Uważnego Czytelnika zapewne zaniepokoił fakt, że <math>\mathbf{State}</math> stoi zarówno po lewej, jak i po prawej stronie równania
stoi zarówno po lewej jak i po prawej stronie równania
<math>
<math>
\mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp} \times \mathbf{State}.
\mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp} \times \mathbf{State}</math>
</math>
Również zapis <math>s[x \mapsto (e_1, s)]</math> może wzbudzić niepokój, gdyż sugeruje on, iż <math>s(x)</math> zawiera jako jeden z elementów pary obiekt "tego samego typu" co <math>s</math>.
Również zapis <math> s[x \mapsto (e_1, s)] </math> może wzbudzić niepokój,
gdyż sugeruje on, iż <math> s(x) </math> zawiera, jako jeden z elementów pary,
obiekt ''tego samego typu'' co <math> s </math>.
Formalnego rozwiązania tego typu dylematów dostarcza teoria dziedzin.
Formalnego rozwiązania tego typu dylematów dostarcza teoria dziedzin.
Natomiast na użytek semantyki operacyjnej wystarczy, jeśli  
Natomiast na użytek semantyki operacyjnej wystarczy, jeśli uznamy, iż równanie  
uznamy, iż równanie  
<math>
<math>
\mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp} \times \mathbf{State}
\mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp} \times \mathbf{State}
</math>
</math>
stanowi skrótowy zapis następującej definicji.
stanowi skrótowy zapis następującej definicji.
Zdefiniujmy <math> \mathbf{State}_0, \mathbf{State}_1, \ldots </math> następująco:
Zdefiniujmy <math>\mathbf{State}_0, \mathbf{State}_1, \ldots</math> następująco:


<math>
<math>
Linia 151: Linia 141:


<math>
<math>
\mathbf{State} = \bigcup_{n \in \nat} \mathbf{State}_{n}
\mathbf{State} = \bigcup_{n \in nat} \mathbf{State}_{n}
</math>
</math>


Tranzycje będą znów postaci:
Tranzycje będą znów postaci:
<math>
<math>
e, s \,\longrightarrow\, n.
e, s \,\longrightarrow\, n</math>
</math>
Podamy tylko reguły dla wystąpienia zmiennej i dla wyrażenia <math>\mathbf{let}\ </math>,.  
Podamy tylko reguły dla wystąpienia zmiennej i dla wyrażenia
Reguły dla pozostałych konstrukcji języka pozostają praktycznie bez zmian.
<math> \mathbf{let}\, </math>. Pozostałe reguły pozostają praktyczanie bez zmian.


<math>
<math>
\frac{e, s' \,\longrightarrow\, n}
\frac{e, s' \,\longrightarrow\, n}
     {x, s \,\longrightarrow\, n} \quad \mbox{ o ile } s(x) = (e, s')
     {x, s \,\longrightarrow\, n} \quad \mbox{ o ile } s(x) = (e, s')
</math>
</math>


<math>
<math>
\frac{e_2, s[x \mapsto (e_1, s)] \,\longrightarrow\, n}
\frac{e_2, s[x \mapsto (e_1, s)] \,\longrightarrow\, n}
     {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, n}
     {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, n}
</math>  
</math>  


</div></div>


==== Zadanie 3 (semantyka dynamiczna) ====


Rozważmy teraz zupełnie inny mechanizm wiązania identyfikatorów,
 
zwany ''wiązaniem dynamicznym''.
{{cwiczenie|3 (semantyka dynamiczna)|cw3|
Dla odróżnienia, dotychczasowy sposób wiązania (widoczności)
 
identyfikatorów będziemy nazywać ''wiązaniem statycznym''.  
 
Rozważmy teraz zupełnie inny mechanizm wiązania identyfikatorów zwany ''wiązaniem dynamicznym''.
Dla odróżnienia, dotychczasowy sposób wiązania (widoczności) identyfikatorów będziemy nazywać ''wiązaniem statycznym''.  
Oto przykładowe wyrażenie:
Oto przykładowe wyrażenie:


Linia 184: Linia 175:
</math>
</math>


które nie ma wartości według semantyk z poprzednich zadań, ponieważ
które nie ma wartości w pustym stanie początkowym, według semantyk z poprzednich zadań, ponieważ odwołanie do zmiennej <math>x</math> w deklaracji <math>y = x+1</math> jest niepoprawne.
odwołanie do zmiennej <math> x </math> w deklaracji <math> y = x+1 </math>
Tak samo jest nawet w semantyce leniwej, gdyż wartość zmiennej <math>y</math> będzie w końcu policzona i będzie wymagała odwołania do <math>x</math> w stanie pustym.
jest niepoprawne (w pustym stanie początkowym).
Tak samo jest nawet w semantyce leniwej, gdyż wartość zmiennej
<math> y </math> będzie w końcu policzona, i będzie wymagała odwołania do <math> x
</math> w stanie pustym.


Natomiast wyobrażmy sobie, że zmieniamy semantykę leniwą następująco:
Natomiast wyobrażmy sobie, że zmieniamy semantykę leniwą następująco: odwołanie do zmiennej <math>x</math> podczas obliczania wartości <math>y</math> będzie odnosiło się nie do stanu w momencie deklaracji <math>y</math>, ale do stanu w momencie ''odwołania'' do <math>y</math>.
odwołanie do zmiennej <math> x </math> podczas obliczania wartości <math> y </math>
Jest to dość rewolucyjna zmiana, zapewne sprzeczna z intuicjami programisty (statyczne reguły widoczności zamieniamy na ''dynamiczne'').  
będzie odnosiło się nie do stanu w momencie deklaracji <math> y </math>,
W szczególności powyższe wyrażenie policzy się w semantyce dynamicznej do wartości 11, ponieważ stan w momencie odwołania do zmiennej <math>y</math> przypisuje zmiennej <math>x</math> wartość 10 !
ale do stanu w momencie ''odwołania'' do <math> y </math>.
Jest to dość rewolucyjna zmiana, zapewne sprzeczna z intuicjami
programisty (statyczne reguły widoczności zamieniamy na
''dynamiczne''). W szczególności powyższe wyrażenie policzy się
w semantyce dynamicznej do wartości 11, ponieważ stan w momencie
odwołania do zmiennej <math> y </math> przypisuje zmiennej <math> x </math>
wartość 10 !


Napisz semantykę naturalną dla wiązania dynamicznego.
Napisz semantykę naturalną dla wiązania dynamicznego.
}}




==== Rozwiązanie ====
<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">


Teraz w stanie wystarczy przechowywać ''wyrażenie'' definiujące
Teraz w stanie wystarczy przechowywać "wyrażenie" definiujące wartość danej zmiennej:
wartość danej zmiennej:


<math>
<math>
\mathbf{State} = \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp}.
\mathbf{State} = \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp}</math>
</math>


Nie potrzebujemy zapamiętywać stanu, w którym byliśmy w momencie
Nie potrzebujemy zapamiętywać stanu, w którym byliśmy w momencie deklaracji.  
deklaracji. Do obliczenia zapamiętanego wyrażenia użyjemy stanu,
Do obliczenia zapamiętanego wyrażenia użyjemy stanu, w którym będziemy w momencie odwołania do danej zmiennej.
w którym będziemy w momencie odwołania do danej zmiennej.
Znów podajemy tylko reguły dla wystąpienia zmiennej i dla wyrażenia <math>\mathbf{let}\ </math>,, gdyż pozostałe reguły pozostają bez zmian
Znów podajemy tylko reguły dla wystąpienia zmiennej i dla
wyrażenia <math> \mathbf{let}\, </math>, gdyż pozostałe reguły pozostają bez zmian


<math>
<math>
\frac{e, s \,\longrightarrow\, n}
\frac{e, s \,\longrightarrow\, n}
     {x, s \,\longrightarrow\, n} \quad \mbox{ o ile } s(x) = e
     {x, s \,\longrightarrow\, n} \quad \mbox{ o ile } s(x) = e
</math>  
</math>  


<math>
<math>
\frac{e_2, s[x \mapsto e_1] \,\longrightarrow\, n}
\frac{e_2, s[x \mapsto e_1] \,\longrightarrow\, n}
     {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, n}
     {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, n}
</math>
</math>


'''Pytanie:''' czy <math>\mathbf{let}\, x = x+1 \,\mathbf{in}\, x</math> oblicza się do jakiejś wartości w stanie <math>\emptyset</math>?


</div></div>


== Prosty język funkcyjny ==
== Prosty język funkcyjny ==




==== Zadanie 1 ====
{{cwiczenie|4 (przekazywanie parametru przez wartość)|cw4|
 
 
Rozważmy prosty język funkcyjny <math>F</math> rozszerzający język wyrażeń z poprzednich zadań następująco:
 
<math>
e \,  ::= \,\, 
        \ldots \,\,|\,\,
        \lambda x.e \,\,|\,\,
        e_1(e_2)
</math>
 
''Lambda-abstrakcja'' <math>\lambda x.e</math> reprezentuje anonimową (nienazwaną) funkcję jednoargumentową, natomiast wyrażenie <math>e_1(e_2)</math> to ''aplikacja'' <math>e_1</math> do <math>e_2</math> (wyrażenie <math>e_1</math> powinno zatem obliczać się do funkcji).
Np.
 
<math>
(\lambda x. x+3)(2) \,\longrightarrow\, 5</math>
 
Przyjmijmy statyczną widoczność identyfikatorów.
Możliwe są różne mechanizmy przekazywania parametrów.
Na razie wybierzmy mechanizm przekazywania przez wartość,
zapewne doskonale znany Czytelnikowi: wyrażenie będące parametrem aktualnym jest obliczane przed wywołaniem funkcji, czyli w stanie, w którym jesteśmy z momencie wywołania funkcji.
 
Zaproponuj semantykę naturalną dla tego języka dla obydwu mechanizmów przekazywania parametrów.
}}
 
 
 
<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">
 
Zauważmy, że oprócz deklaracji zmiennych, również machanizm przekazywania parametru do funkcji wymaga zmiany stanu.
 
Tranzycje będą postaci
 
<math>
e, s \,\longrightarrow\, v
</math>
 
gdzie <math>v</math> reprezentuje wartość, do której oblicza się program <math>e</math>.
Z tym, że wartościami będą nie tylko wartości liczbowe. Na przykład
<math>
\lambda x. x
</math>
oblicza się również do pewnej wartość, która w tym przypadku powinna reprezentować jakoś ''funkcję'' identycznościową. Wystarczającą reprezentacją funkcji będzie trójka:
<math>\langle x, e, s \rangle</math>, gdzie <math>x</math> jest nazwą parametru formalnego, <math>e</math> jest ciałem funkcji, a <math>s</math> jest stanem, w którym należy obliczać wartość funkcji po zaaplikowaniu do jakiegoś parametru aktualnego.
Oto prosty przykład pokazujący, dlaczego powinniśmy pamiętać stan:


....
<math>
\mathbf{let}\, x = 7 \,\mathbf{in}\, \mathbf{let}\, f = \lambda z.x+z \,\mathbf{in}\, \mathbf{let}\, x = f(1) \,\mathbf{in}\, f(10)</math>


Funkcja <math>f</math> zwiększa parametr aktualny o <math>x</math>.
Program oblicza się do wartości <math>17</math>, gdyż wystąpienie zmiennej <math>x</math> w ciele funkcji <math>f</math> wiąże statycznie, a zatem odnosi się zawsze do deklaracji <math>x = 7</math>, mimo tego, że w momencie wywołania tej funkcji wartość zmiennej <math>x</math> wynosi <math>8</math>.


==== Rozwiązanie ====
Oto jedyna reguła, jakiej będziemy potrzebować dla lambda-abstrakcji:


<math>
\lambda x.e, s \,\longrightarrow\, \langle x, e, s \rangle</math>


Nie potrafimy zrobić z funkcją <math>\lambda x. e</math> nic innego jak zapamiętać informację niezbędną do obliczania jej wartości w przyszłości.
Zatem zbiór wartości będzie następujący:
<math>
v \in  \mathbf{Values} = \mathbf{Num} \cup (\mathbf{Var} \times \mathbf{Exp} \times \mathbf{State})
</math>
a zbiór stanów <math>\mathbf{State}</math> określony jest następującym równaniem:
<math>
\mathbf{State} = \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Values}</math>
Zacznijmy od stałych, zmiennych i lambda-abstrakcji:
<math>
n, s \,\longrightarrow\, n
\quad \quad
x, s \,\longrightarrow\, v \quad \mbox{ o ile } s(x) = v
\quad \quad
\lambda x.e, s \,\longrightarrow\, \langle x, e, s \rangle
</math>
W regule dla zmiennej, <math>v</math> oznacza albo wartość liczbową albo funkcyjną.
Pomijamy regułę dla dodawania, bo jest ona identyczna jak dla gorliwej semantyki wyrażeń.
Reguła dla <math>\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2</math> będzie prawie taka sama jak dla wyrażeń, z tą różnicą, że wyrażenie <math>e_1</math> definiujące wartość zmiennej <math>x</math> może się teraz obliczać do wartości funkcyjnej, np. <math>\mathbf{let}\, x = (\lambda y.y+y) \,\mathbf{in}\, x(0)</math>.
<math>
\frac{e_1, s \,\longrightarrow\, v_1 \quad e_2, s[x \mapsto v_1] \,\longrightarrow\, v_2}
    {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, v_2}
</math>
Pozostała nam już tylko reguła dla aplikacji: najpierw oblicz funkcję; następnie oblicz wartość parametru aktualnego; wreszcie przekaż ją do ciała funkcji (czyli oblicz ciało funkcji w zmodyfikowanym stanie):
<math>
\frac{e_1, s \,\longrightarrow\, \langle x, e, s' \rangle \quad \quad
      e_2, s \,\longrightarrow\, v' \quad \quad
      e, s'[x \mapsto v'] \,\longrightarrow\, v}
    {e_1(e_2), s \,\longrightarrow\, v}
</math>
Zwróćmy uwagę na wymóg, że <math>e_1</math> oblicza się do wartości funkcyjnej <math>\langle x, e, s' \rangle</math>.
W szczególności np. wyrażenie <math>7(3+4)</math> jest niepoprawne.
Natomiast parametr aktualny nie musi być liczbą, może być funkcją, np. w programie:
<math>
\mathbf{let}\, g = \lambda f. \lambda x. f(x) \,\mathbf{in}\,
g(\lambda x.x+1)(7)
</math>
który oblicza się do wartości <math>8</math>.
</div></div>
{{cwiczenie|5 (przekazywanie parametru przez nazwę)|cw5|
Zaproponuj ''leniwą'' semantykę języka <math>F</math> z mechnizmem przekazywanie parametru "przez nazwę".
Mechanizm ten stanowi leniwy odpowiednik przekazywania przez wartość: nie obliczamy wyrażenia będącego parametrem aktualnym, a zamiast jego wartości przekazujemy do funkcji to wyrażenie wraz ze
stanem z miejsca wywołania funkcji.
To ten stan bedzie brany pod uwagę, gdy obliczana będzie wartość parametru, tzn. przy odwołaniu w ciele funkcji do parametru formalnego. Oto przykład programu:
<math>
\mathbf{let}\, f = \lambda x. 7 \,\mathbf{in}\, f(y)
</math>
który w stanie pustym (wszystkie zmienne nieokreślone)
nie ma wartości przy przekazywaniu parametru przez wartość
(bo odwołanie do zmiennej <math>y</math> jest niepoprawne), a oblicza się
do wartości <math>7</math> jeśli wybierzemy mechanizm przekazywania przez
nazwę.
}}
<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">
Zbiór wartości <math>\mathbf{Values}</math> stojących po prawej stronie symbolu <math>\,\longrightarrow\ </math>, będzie taki sam jak w poprzednim zadaniu.
Natomiast zbiór stanów taki sam jak w semantyce leniwej wyrażeń:
<math>
\mathbf{State} = \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Exp} \times \mathbf{State}</math>
Podamy tylko trzy reguły: dla wystąpienie zmiennej, deklaracji <math>\mathbf{let}\ </math>, i aplikacji -- wszystkie pozostałe reguły pozostają takie same jak w poprzednim zadaniu.
<math>
\frac{e, s' \,\longrightarrow\, v}
    {x, s \,\longrightarrow\, v} \quad \mbox{ o ile } s(x) = (e, s')
</math>
<math>
\frac{e_2, s[x \mapsto (e, s)] \,\longrightarrow\, v}
    {\mathbf{let}\, x = e_1 \,\mathbf{in}\, e_2, s \,\longrightarrow\, v}
</math>
<math>
\frac{e_1, s \,\longrightarrow\, \langle x, e, s' \rangle \quad \quad
      e, s'[x \mapsto (e_2, s)] \,\longrightarrow\, v}
    {e_1(e_2), s \,\longrightarrow\, v}
</math>
Podstawowa różnica w ostatnej regule w porównaniu do poprzedniego zadania to ''brak ewaluacji'' parametru aktualnego <math>e_2</math>.
Zwróćmy też uwagę na wyrażenie <math>s'[x \mapsto (e_2, s)]</math>, w którym <math>s \neq s'</math>.
Stany, których potrzebowaliśmy dotychczas podczas poprzednich zajęć, miały zawsze postać <math>s[x \mapsto (e, s)]</math>.
</div></div>


== Zadania domowe ==
== Zadania domowe ==




==== Zadanie 1 ====
{{cwiczenie|1|cw1.dom|


Podaj przykład wyrażenia takiego, które:
Podaj przykład wyrażenia takiego, które:


* ma wartość w semantyce statycznej i dynamicznej, ale w każdej inną
* ma wartość w semantyce statycznej i dynamicznej, ale w każdej inną
* ma wartość w semantyce leniwej a nie ma w dynamicznej
* ma wartość w semantyce leniwej, a nie ma w dynamicznej
* ma wartość w semantyce dynamicznej a nie ma w leniwej
* ma wartość w semantyce dynamicznej, a nie ma w leniwej.
}}
 
 
{{cwiczenie|2|cw2.dom|
 
W semantyce leniwej wyrażeń, jeśli jest wiele odwołań do jakiejś zmiennej, to obliczenie wartości tej zmiennej nastąpi za każdym razem od nowa.
Zmodyfikuj tę semantykę tak, aby wartość ta była obliczana ''co najwyżej'' raz.
Zatem po pierwszym odwołaniu do zmiennej, jej obliczona wartość powinna zostać umieszczona w stanie, zastępując parę (wyrażenie, stan).
}}
 
 
{{cwiczenie|3|cw3.dom|
 
Zaproponuj ''dynamiczne'' odpowiedniki obydwu ''statycznych'' semantyk dla języka funkcyjnego <math>F</math>.
Czyli zakładamy, że widoczność identyfikatorów, m.in. w ciele funkcji, jest dynamiczna.
Oto przykład programu, który w semantyce statycznej oblicza się do wartości <math>12</math>, a w dynamicznej do wartości <math>5</math>
(parametr przekazywany przez wartość):
 
<math>
\mathbf{let}\, x = 7 \,\mathbf{in}\,
\mathbf{let}\, f = \lambda z. z+x \,\mathbf{in}\,
\mathbf{let}\, x = 0 \,\mathbf{in}\, f(5)
</math>
 
Rozważ dwa mechanizmy przekazywania parametrów:
 
* przez wartość
* przez nazwę
 
Ten drugi mechanizm rozumiemy teraz następująco: parametr aktualny nie jest obliczany w momencie zaaplikowania do niego funkcji,
a do ciała funkcji przekazuje się wyrażenie będące parametrem aktualnym.
W momencie odwołania do parametru formalnego w ciele funkcji, wyrażenie
będące parametrem aktualnym jest obliczane w bieżącym stanie (a nie w stanie z miejsca wywołania funkcji).
Jako przykład rozważmy program:
 
<math>
(\lambda z. \mathbf{let}\, x = 10 \,\mathbf{in}\, z+z)(x) + 1
</math>
 
Przy przekazywaniu przez wartość w stanie pustym program się nie obliczy, ponieważ nie da się obliczyć parametru aktualnego <math>x</math>.
Natomiast przy przekazywaniu przez nazwę, parametr aktualny będzie obliczany dopiero w momencie odwołania do parametru formalnego <math>z</math>, czyli w momencie obliczania wartości wyrażenia <math>z + z</math>.
W stanie tym zmienna <math>x</math> ma już wartość, a zatem wartością całego programu będzie 21.
}}

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

Zawartość

Napiszemy semantykę naturalną języka wyrażeń, rozważymy strategię gorliwą (jak na wcześniejszych zajęciach, w semantyce małych kroków) i leniwą. Rozważymy i statyczne i dynamiczne wiązanie identyfikatorów. Następnie rozszerzymy ten język o lambda-abstrakcję i aplikację, otrzymując prosty język funkcyjny.


Różne semantyki naturalne wyrażeń

Ćwiczenie 1 (semantyka gorliwa)


Napisz semantykę dużych kroków dla języka wyrażeń, którego semantykę małokrokową napisaliśmy na jednych z poprzednich ćwiczeń:

n::=0|1|

x::=(identyfikatory)

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


Rozwiązanie


Ćwiczenie 2 (semantyka leniwa)


Zmodyfikuj semantykę z poprzedniego zadania, aby uzyskać leniwą ewaluację wyrażeń, zgodnie z dyrektywą: nie obliczaj wyrażenia, o ile jego wynik nie jest potrzebny (albo: obliczaj wartość wyrażenia dopiero wtedy, gdy jego wynik jest naprawdę potrzebny). Spójrzmy na przykład:

𝐥𝐞𝐭x=7𝐢𝐧𝐥𝐞𝐭y=y+y𝐢𝐧x+x

Według semantyki z poprzedniego zadania wyrażnie to nie ma wartości, bo w deklaracji y=y+y jest odwołanie do niezainicjowanej zmiennej. Natomiast w semantyce leniwej wyrażenie to obliczy się do wartości 14, gdyż wyrażenie y+y nie będzie wogóle obliczane. Będzie tak dlatego, że w wyrażeniu x+x nie ma odwołań do zmiennej y.


Rozwiązanie


Ćwiczenie 3 (semantyka dynamiczna)


Rozważmy teraz zupełnie inny mechanizm wiązania identyfikatorów zwany wiązaniem dynamicznym. Dla odróżnienia, dotychczasowy sposób wiązania (widoczności) identyfikatorów będziemy nazywać wiązaniem statycznym. Oto przykładowe wyrażenie:

𝐥𝐞𝐭y=x+1𝐢𝐧𝐥𝐞𝐭x=10𝐢𝐧y

które nie ma wartości w pustym stanie początkowym, według semantyk z poprzednich zadań, ponieważ odwołanie do zmiennej x w deklaracji y=x+1 jest niepoprawne. Tak samo jest nawet w semantyce leniwej, gdyż wartość zmiennej y będzie w końcu policzona i będzie wymagała odwołania do x w stanie pustym.

Natomiast wyobrażmy sobie, że zmieniamy semantykę leniwą następująco: odwołanie do zmiennej x podczas obliczania wartości y będzie odnosiło się nie do stanu w momencie deklaracji y, ale do stanu w momencie odwołania do y. Jest to dość rewolucyjna zmiana, zapewne sprzeczna z intuicjami programisty (statyczne reguły widoczności zamieniamy na dynamiczne). W szczególności powyższe wyrażenie policzy się w semantyce dynamicznej do wartości 11, ponieważ stan w momencie odwołania do zmiennej y przypisuje zmiennej x wartość 10 !

Napisz semantykę naturalną dla wiązania dynamicznego.


Rozwiązanie

Prosty język funkcyjny

Ćwiczenie 4 (przekazywanie parametru przez wartość)


Rozważmy prosty język funkcyjny F rozszerzający język wyrażeń z poprzednich zadań następująco:

e::=|λx.e|e1(e2)

Lambda-abstrakcja λx.e reprezentuje anonimową (nienazwaną) funkcję jednoargumentową, natomiast wyrażenie e1(e2) to aplikacja e1 do e2 (wyrażenie e1 powinno zatem obliczać się do funkcji). Np.

(λx.x+3)(2)5

Przyjmijmy statyczną widoczność identyfikatorów. Możliwe są różne mechanizmy przekazywania parametrów. Na razie wybierzmy mechanizm przekazywania przez wartość, zapewne doskonale znany Czytelnikowi: wyrażenie będące parametrem aktualnym jest obliczane przed wywołaniem funkcji, czyli w stanie, w którym jesteśmy z momencie wywołania funkcji.

Zaproponuj semantykę naturalną dla tego języka dla obydwu mechanizmów przekazywania parametrów.


Rozwiązanie


Ćwiczenie 5 (przekazywanie parametru przez nazwę)


Zaproponuj leniwą semantykę języka F z mechnizmem przekazywanie parametru "przez nazwę". Mechanizm ten stanowi leniwy odpowiednik przekazywania przez wartość: nie obliczamy wyrażenia będącego parametrem aktualnym, a zamiast jego wartości przekazujemy do funkcji to wyrażenie wraz ze stanem z miejsca wywołania funkcji. To ten stan bedzie brany pod uwagę, gdy obliczana będzie wartość parametru, tzn. przy odwołaniu w ciele funkcji do parametru formalnego. Oto przykład programu:

𝐥𝐞𝐭f=λx.7𝐢𝐧f(y)

który w stanie pustym (wszystkie zmienne nieokreślone) nie ma wartości przy przekazywaniu parametru przez wartość (bo odwołanie do zmiennej y jest niepoprawne), a oblicza się do wartości 7 jeśli wybierzemy mechanizm przekazywania przez nazwę.


Rozwiązanie

Zadania domowe

Ćwiczenie 1

Podaj przykład wyrażenia takiego, które:

  • ma wartość w semantyce statycznej i dynamicznej, ale w każdej inną
  • ma wartość w semantyce leniwej, a nie ma w dynamicznej
  • ma wartość w semantyce dynamicznej, a nie ma w leniwej.


Ćwiczenie 2

W semantyce leniwej wyrażeń, jeśli jest wiele odwołań do jakiejś zmiennej, to obliczenie wartości tej zmiennej nastąpi za każdym razem od nowa. Zmodyfikuj tę semantykę tak, aby wartość ta była obliczana co najwyżej raz. Zatem po pierwszym odwołaniu do zmiennej, jej obliczona wartość powinna zostać umieszczona w stanie, zastępując parę (wyrażenie, stan).


Ćwiczenie 3

Zaproponuj dynamiczne odpowiedniki obydwu statycznych semantyk dla języka funkcyjnego F. Czyli zakładamy, że widoczność identyfikatorów, m.in. w ciele funkcji, jest dynamiczna. Oto przykład programu, który w semantyce statycznej oblicza się do wartości 12, a w dynamicznej do wartości 5 (parametr przekazywany przez wartość):

𝐥𝐞𝐭x=7𝐢𝐧𝐥𝐞𝐭f=λz.z+x𝐢𝐧𝐥𝐞𝐭x=0𝐢𝐧f(5)

Rozważ dwa mechanizmy przekazywania parametrów:

  • przez wartość
  • przez nazwę

Ten drugi mechanizm rozumiemy teraz następująco: parametr aktualny nie jest obliczany w momencie zaaplikowania do niego funkcji, a do ciała funkcji przekazuje się wyrażenie będące parametrem aktualnym. W momencie odwołania do parametru formalnego w ciele funkcji, wyrażenie będące parametrem aktualnym jest obliczane w bieżącym stanie (a nie w stanie z miejsca wywołania funkcji). Jako przykład rozważmy program:

(λz.𝐥𝐞𝐭x=10𝐢𝐧z+z)(x)+1

Przy przekazywaniu przez wartość w stanie pustym program się nie obliczy, ponieważ nie da się obliczyć parametru aktualnego x. Natomiast przy przekazywaniu przez nazwę, parametr aktualny będzie obliczany dopiero w momencie odwołania do parametru formalnego z, czyli w momencie obliczania wartości wyrażenia z+z. W stanie tym zmienna x ma już wartość, a zatem wartością całego programu będzie 21.