Semantyka i weryfikacja programów/Ćwiczenia 4: Różnice pomiędzy wersjami
Nie podano opisu zmian |
Nie podano opisu zmian |
||
Linia 1: | Linia 1: | ||
== | == Zawartość == | ||
Napiszemy | Napiszemy semantykę naturalną języka wyrażeń, | ||
rozważymy strategię gorliwą (jak na wcześniejszych zajęciach, | |||
w semantyce | 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ń == | ||
Linia 17: | Linia 17: | ||
Napisz | Napisz semantykę dużych kroków | ||
dla | dla języka wyrażeń, którego semantykę mało-krokową | ||
napisaliśmy na jednych z poprzednich ćwiczeń: | |||
<math> | <math> | ||
Linia 44: | Linia 44: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | ||
Przypomnijmy, | Przypomnijmy, że zbiór stanów to | ||
<math> | <math> | ||
\mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Num} | \mathbf{State} \, = \, \mathbf{Var} \to_{\mathrm{fin}} \mathbf{Num} | ||
</math> | </math> | ||
Nasze tranzycje | 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 | Oto reguły semantyki naturalnej. | ||
<math> | <math> | ||
Linia 81: | Linia 81: | ||
</math> | </math> | ||
Zwróćmy uwagę na fakt, że prawidłowe odwzorowanie zasięgu deklaracji <math>x = e_1</math> nie predstawia | |||
w semantyce naturalnej | w semantyce naturalnej żadnych trudności, w przeciwieństwie do | ||
semantyki | semantyki małych kroków. | ||
</div></div> | </div></div> | ||
Linia 92: | Linia 92: | ||
Zmodyfikuj | 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 | (albo: obliczaj wartość wyrażenia dopiero wtedy, gdy jego wynik jest | ||
naprawdę potrzebny). Spójrzmy na przykład: | |||
<math> | <math> | ||
Linia 102: | Linia 102: | ||
</math> | </math> | ||
Według semantyki z poprzedniego zadania wyrażnie to nie ma wartości, | |||
bo w deklaracji <math>y = y+y</math> jest | bo w deklaracji <math>y = y+y</math> jest odwołanie do niezainicjowanej | ||
zmiennej. | zmiennej. | ||
Natomiast w semantyce leniwej | Natomiast w semantyce leniwej wyrażenie to obliczy się do wartości | ||
<math>14</math>, | <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>. | zmiennej <math>y</math>. | ||
}} | }} | ||
Linia 116: | Linia 116: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | ||
Semantyka ''leniwa'' | Semantyka ''leniwa'' będzie bardzo podobna do tej z poprzedniego zadania. | ||
Zasadnicza | Zasadnicza różnica dotyczy informacji przechowywanej w stanie. | ||
Dotychczas <math>s(x)</math> | 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> | <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> | <math>s(x)</math> powinniśmy zapamiętać całe (nieobliczone) wyrażenie <math>e</math> | ||
wraz ze stanem | wraz ze stanem bieżącym. | ||
Czyli | Czyli | ||
Linia 129: | Linia 129: | ||
</math> | </math> | ||
Np. odpowiednia | Np. odpowiednia reguła dla wyrażenia <math>\mathbf{let}\,</math> w semantyce | ||
małych kroków mogłaby wyglądać następująco: | |||
<math> | <math> | ||
Linia 136: | Linia 136: | ||
</math> | </math> | ||
Czyli stan zawiera, dla | Czyli stan zawiera, dla każdej zmiennej, parę | ||
( | (wyrażenie definiujące, stan w momencie deklaracji). | ||
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>. | obiekt ''tego samego typu'' co <math>s</math>. | ||
Formalnego | Formalnego rozwiązania tego typu dylematów dostarcza teoria dziedzin. | ||
Natomiast na | Natomiast na użytek semantyki operacyjnej wystarczy, jeśli | ||
uznamy, | 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 | stanowi skrótowy zapis następującej definicji. | ||
Zdefiniujmy <math>\mathbf{State}_0, \mathbf{State}_1, \ldots</math> | Zdefiniujmy <math>\mathbf{State}_0, \mathbf{State}_1, \ldots</math> następująco: | ||
<math> | <math> | ||
Linia 164: | Linia 164: | ||
</math> | </math> | ||
i przyjmijmy, | i przyjmijmy, że | ||
<math> | <math> | ||
Linia 170: | Linia 170: | ||
</math> | </math> | ||
Tranzycje | Tranzycje będą znów postaci: | ||
<math> | <math> | ||
e, s \,\longrightarrow\, n. | e, s \,\longrightarrow\, n. | ||
</math> | </math> | ||
Podamy tylko | Podamy tylko reguły dla wystąpienia zmiennej i dla wyrażenia | ||
<math>\mathbf{let}\,</math>. | <math>\mathbf{let}\,</math>. Reguły dla pozostałych konstrukcji języka pozostają praktycznie bez zmian. | ||
<math> | <math> | ||
Linia 194: | Linia 194: | ||
Rozważmy teraz zupełnie inny mechanizm wiązania identyfikatorów, | |||
zwany '' | zwany ''wiązaniem dynamicznym''. | ||
Dla | Dla odróżnienia, dotychczasowy sposób wiązania (widoczności) | ||
identyfikatorów | identyfikatorów będziemy nazywać ''wiązaniem statycznym''. | ||
Oto | Oto przykładowe wyrażenie: | ||
<math> | <math> | ||
Linia 204: | Linia 204: | ||
</math> | </math> | ||
które nie ma | 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. | jest niepoprawne. | ||
Tak samo jest nawet w semantyce leniwej, | Tak samo jest nawet w semantyce leniwej, gdyż wartość zmiennej | ||
<math>y</math> | <math>y</math> będzie w końcu policzona, i będzie wymagała odwołania do <math>x | ||
</math> w stanie pustym. | </math> w stanie pustym. | ||
Natomiast | 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 '' | ale do stanu w momencie ''odwołania'' do <math>y</math>. | ||
Jest to | Jest to dość rewolucyjna zmiana, zapewne sprzeczna z intuicjami | ||
programisty (statyczne | programisty (statyczne reguły widoczności zamieniamy na | ||
''dynamiczne''). W | ''dynamiczne''). W szczególności powyższe wyrażenie policzy się | ||
w semantyce dynamicznej do | 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 | Napisz semantykę naturalną dla wiązania dynamicznego. | ||
}} | }} | ||
Linia 231: | Linia 231: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | ||
Teraz w stanie wystarczy | Teraz w stanie wystarczy przechowywać ''wyrażenie'' definiujące | ||
wartość danej zmiennej: | |||
<math> | <math> | ||
Linia 238: | Linia 238: | ||
</math> | </math> | ||
Nie potrzebujemy | Nie potrzebujemy zapamiętywać stanu, w którym byliśmy w momencie | ||
deklaracji. Do obliczenia | deklaracji. Do obliczenia zapamiętanego wyrażenia użyjemy stanu, | ||
w którym | w którym będziemy w momencie odwołania do danej zmiennej. | ||
Znów podajemy tylko | 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> | ||
Linia 254: | Linia 254: | ||
</math> | </math> | ||
'''Pytanie:''' czy <math>\mathbf{let}\, x = x+1 \,\mathbf{in}\, x</math> oblicza | '''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> | </div></div> | ||
Linia 261: | Linia 261: | ||
== Prosty | == Prosty język funkcyjny == | ||
{{cwiczenie|4 (przekazywanie parametru przez | {{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> | <math> | ||
Linia 277: | Linia 277: | ||
</math> | </math> | ||
''Lambda-abstrakcja'' <math>\lambda x.e</math> reprezentuje | ''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> ( | <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 | <math>e_1</math> powinno zatem obliczać się do funkcji). | ||
Np. | Np. | ||
Linia 287: | Linia 287: | ||
</math> | </math> | ||
Przyjmijmy | Przyjmijmy statyczną widoczność identyfikatorów. | ||
Możliwe są różne mechanizmy przekazywania parametrów. | |||
Na razie wybierzmy mechanizm przekazywania przez | Na razie wybierzmy mechanizm przekazywania przez wartość, | ||
zapewne doskonale znany Czytelnikowi: | zapewne doskonale znany Czytelnikowi: wyrażenie | ||
będące parametrem aktualnym jest obliczane przed wywołaniem funkcji, | |||
czyli w stanie, w którym | czyli w stanie, w którym jesteśmy z momencie wywołania funkcji. | ||
Zaproponuj | Zaproponuj semantykę naturalną dla tego języka | ||
dla obydwu mechanizmów przekazywania parametrów. | dla obydwu mechanizmów przekazywania parametrów. | ||
}} | }} | ||
Linia 303: | Linia 303: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><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. | machanizm przekazywania parametru do funkcji wymaga zmiany stanu. | ||
Tranzycje | Tranzycje będą postaci | ||
<math> | <math> | ||
Linia 312: | Linia 312: | ||
</math> | </math> | ||
gdzie <math>v</math> reprezentuje | gdzie <math>v</math> reprezentuje wartość, do której oblicza się program <math>e</math>. | ||
Z tym, | Z tym, że wartościami będą nie tylko wartości liczbowe. Na przykład | ||
<math> | <math> | ||
\lambda x. x | \lambda x. x | ||
</math> | </math> | ||
oblicza | oblicza się również do pewnej wartość, która w tym przypadku powinna reprezentować | ||
jakoś ''funkcję'' iudentycznościową. Wystarczającą reprezentacją | |||
funkcji | funkcji będzie trójka: | ||
<math>\langle x, e, s \rangle</math>, | <math>\langle x, e, s \rangle</math>, | ||
gdzie <math>x</math> jest | gdzie <math>x</math> jest nazwą parametru formalnego, <math>e</math> jest ciałem | ||
funkcji a <math>s</math> jest stanem, w którym | funkcji a <math>s</math> jest stanem, w którym należy obliczać wartość | ||
funkcji po zaaplikowaniu do | funkcji po zaaplikowaniu do jakiegoś parametru aktualnego. | ||
Oto prosty | Oto prosty przykład pokazujący, dlaczego powinniśmy pamiętać stan: | ||
<math> | <math> | ||
Linia 330: | Linia 330: | ||
</math> | </math> | ||
Funkcja <math>f</math> | Funkcja <math>f</math> zwiększa parametr aktualny o <math>x</math>. | ||
Program oblicza | Program oblicza się do wartości <math>17</math>, gdyż wystąpienie | ||
zmiennej <math>x</math> w ciele funkcji <math>f</math> | zmiennej <math>x</math> w ciele funkcji <math>f</math> wiąże statycznie | ||
a zatem odnosi | 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>. | wynosi <math>8</math>. | ||
Oto jedyna | Oto jedyna reguła, jakiej będziemy potrzebować dla lambda-abstrakcji: | ||
<math> | <math> | ||
Linia 343: | Linia 343: | ||
</math> | </math> | ||
Nie potrafimy | Nie potrafimy zrobić z funkcją <math>\lambda x. e</math> nic innego jak | ||
zapamiętać informację niezbędną do obliczania jej wartości | |||
w | w przyszlości. | ||
Zatem zbiór | Zatem zbiór wartości bedzie następujący: | ||
<math> | <math> | ||
Linia 352: | Linia 352: | ||
</math> | </math> | ||
a zbiór stanów <math>\mathbf{State}</math> | a zbiór stanów <math>\mathbf{State}</math> określony jest następującym równaniem: | ||
<math> | <math> | ||
Linia 358: | Linia 358: | ||
</math> | </math> | ||
Zacznijmy od | Zacznijmy od stałych, zmiennych i lambda-abstrakcji: | ||
<math> | <math> | ||
Linia 368: | Linia 368: | ||
</math> | </math> | ||
W regule dla zmiennej, <math>v</math> oznacza albo | 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 | 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 | 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>\mathbf{let}\, x = (\lambda y.y+y) \,\mathbf{in}\, x(0)</math>. | ||
Linia 381: | Linia 381: | ||
</math> | </math> | ||
Pozostała nam już tylko reguła dla aplikacji: najpierw oblicz funkcję; | |||
następnie oblicz wartość parametru aktualnego; wreszcie przekaż ją | |||
do | do ciała funkcji (czyli oblicz ciało funkcji w zmodyfikowanym stanie): | ||
<math> | <math> | ||
Linia 392: | Linia 392: | ||
</math> | </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>. | funkcyjnej <math>\langle x, e, s' \rangle</math>. | ||
W | W szczególności np. wyrażenie <math>7(3+4)</math> jest | ||
niepoprawne. Natomiast parametr aktualny nie musi | niepoprawne. Natomiast parametr aktualny nie musi być liczbą, może być | ||
funkcją, np. w programie: | |||
<math> | <math> | ||
Linia 403: | Linia 403: | ||
</math> | </math> | ||
który oblicza | który oblicza się do wartości <math>8</math>. | ||
</div></div> | </div></div> | ||
Linia 409: | Linia 409: | ||
{{cwiczenie|5 (przekazywanie parametru przez | {{cwiczenie|5 (przekazywanie parametru przez nazwę)|cw5| | ||
Zaproponuj '' | Zaproponuj ''leniwą'' semantykę języka <math>F</math> z mechnizmem | ||
przekazywanie parametru ''przez | przekazywanie parametru ''przez nazwę''. | ||
Mechanizm ten stanowi leniwy odpowiednik przekazywania przez | Mechanizm ten stanowi leniwy odpowiednik przekazywania przez wartość: | ||
nie obliczamy | nie obliczamy wyrażenia będącego parametrem aktualnym, a zamiast | ||
jego | jego wartości przekazujemy do funkcji to wyrażenie wraz ze | ||
stanem z miejsca | stanem z miejsca wywołania funkcji. | ||
To ten stan bedzie brany pod | To ten stan bedzie brany pod uwagę, gdy obliczana będzie | ||
wartość parametru, tzn. przy odwołaniu w ciele funkcji do | |||
parametru formalnego. Oto | parametru formalnego. Oto przykład programu: | ||
<math> | <math> | ||
Linia 426: | Linia 426: | ||
</math> | </math> | ||
który w stanie pustym (wszystkie zmienne | który w stanie pustym (wszystkie zmienne nieokreślone) | ||
nie ma | nie ma wartości przy przekazywaniu parametru przez wartość | ||
(bo | (bo odwołanie do zmiennej <math>y</math> jest niepoprawne) a oblicza się | ||
do | do wartości <math>7</math> jeśli wybierzemy mechanizm przekazywania przez | ||
nazwę. | |||
}} | }} | ||
Linia 438: | Linia 438: | ||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | <div class="mw-collapsible mw-made=collapsible mw-collapsed"><div class="mw-collapsible-content" style="display:none"> | ||
Zbiór | 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 | Natomiast zbiór stanów taki sam jak w semantyce leniwej wyrażeń: | ||
<math> | <math> | ||
Linia 446: | Linia 446: | ||
</math> | </math> | ||
Podamy tylko trzy | Podamy tylko trzy reguły: dla wystąpienie zmiennej, deklaracji <math>\mathbf{let}\,</math> | ||
i aplikacji -- wszystkie | i aplikacji -- wszystkie pozostałe reguły pozostają właściwie | ||
takie same jak w poprzednim zadaniu. | takie same jak w poprzednim zadaniu. | ||
Linia 466: | Linia 466: | ||
</math> | </math> | ||
Podstawowa | Podstawowa różnica w ostatnej regule w porównaniu do poprzedniego | ||
zadania to ''brak ewaluacji'' parametru aktualnego <math>e_2</math>. | 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>. | <math>s'[x \mapsto (e_2, s)]</math>, w którym <math>s \neq s'</math>. | ||
Stany, których | Stany, których potrzebowaliśmy dotychczas podczas poprzednich zajęć, miały zawsze postać | ||
<math>s[x \mapsto (e, s)</math>. | <math>s[x \mapsto (e, s)</math>. | ||
Linia 482: | Linia 482: | ||
{{cwiczenie|1|cw1.dom| | {{cwiczenie|1|cw1.dom| | ||
Podaj | Podaj przykład wyrażenia takiego, które: | ||
* ma | * ma wartość w semantyce statycznej i dynamicznej, ale w każdej inną | ||
* ma | * ma wartość w semantyce leniwej a nie ma w dynamicznej | ||
* ma | * ma wartość w semantyce dynamicznej a nie ma w leniwej. | ||
}} | }} | ||
Linia 492: | Linia 492: | ||
{{cwiczenie|2|cw2.dom| | {{cwiczenie|2|cw2.dom| | ||
W semantyce leniwej | W semantyce leniwej wyrażeń, jeśli jest wiele odwołań do jakiejś | ||
zmiennej, to obliczenie | zmiennej, to obliczenie wartości tej zmiennej nastąpi za każdym razem | ||
od nowa. Zmodyfikuj | od nowa. Zmodyfikuj tę semantykę tak, aby wartość ta była obliczana | ||
''co | ''co najwyżej'' raz. | ||
Zatem po pierwszym | Zatem po pierwszym odwołaniu do zmiennej, jej obliczona wartość | ||
powinna | powinna zostać umieszczona w stanie, zastępując parę (wyrażenie, stan). | ||
}} | }} | ||
Linia 504: | Linia 504: | ||
Zaproponuj ''dynamiczne'' odpowiedniki obydwu ''statycznych'' semantyk dla | Zaproponuj ''dynamiczne'' odpowiedniki obydwu ''statycznych'' semantyk dla | ||
języka funkcyjnego <math>F</math>. | |||
Czyli | Czyli zakładamy, że widoczność identyfikatorów, m.in. w ciele funkcji, | ||
jest dynamiczna. | jest dynamiczna. | ||
Oto | 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 | (parametr przekazywany przez wartość): | ||
<math> | <math> | ||
Linia 517: | Linia 517: | ||
</math> | </math> | ||
Rozważ dwa mechanizmy przekazywania parametrów: | |||
* przez | * przez wartość | ||
* przez | * przez nazwę | ||
Ten drugi mechanizm rozumiemy teraz | Ten drugi mechanizm rozumiemy teraz następująco. Parametr aktualny nie | ||
jest obliczany w momencie zaaplikowania do niego funkcji, | jest obliczany w momencie zaaplikowania do niego funkcji, | ||
a do | a do ciała funkcji przekazuje się wyrażenie będące parametrem aktualnym. | ||
W momencie | 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 | stanie z miejsca wywołania funkcji). | ||
Jako | Jako przykład pozważmy program: | ||
<math> | <math> | ||
Linia 535: | Linia 535: | ||
</math> | </math> | ||
Przy przekazywaniu przez | Przy przekazywaniu przez wartość, w stanie pustym program się nie | ||
obliczy, | obliczy, ponieważ nie da się obliczyć parametru aktualnego <math>x</math>. | ||
Natomiast przy przekazywaniu przez | Natomiast przy przekazywaniu przez nazwę, parametr aktualny będzie | ||
obliczany dopiero w momencie | obliczany dopiero w momencie odwołania do parametru formalnego <math>z | ||
</math>, czyli w momencie obliczania | </math>, czyli w momencie obliczania wartości wyrażenia <math>z + z</math>. | ||
W stanie tym zmienna <math>x</math> ma | W stanie tym zmienna <math>x</math> ma już wartość, a zatem wartością | ||
całego programu będzie 21. | |||
}} | }} |
Wersja z 08:09, 10 sie 2006
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ło-krokową
napisaliśmy na jednych z poprzednich ćwiczeń:
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:
Według semantyki z poprzedniego zadania wyrażnie to nie ma wartości, bo w deklaracji jest odwołanie do niezainicjowanej zmiennej. Natomiast w semantyce leniwej wyrażenie to obliczy się do wartości , gdyż wyrażenie nie będzie wogóle obliczane. Będzie tak dlatego, że w wyrażeniu nie ma odwołań do zmiennej .
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:
które nie ma wartości w pustym stanie początkowym, według semantyk z poprzednich zadań, ponieważ odwołanie do zmiennej w deklaracji jest niepoprawne. Tak samo jest nawet w semantyce leniwej, gdyż wartość zmiennej będzie w końcu policzona, i będzie wymagała odwołania do w stanie pustym.
Natomiast wyobrażmy sobie, że zmieniamy semantykę leniwą następująco: odwołanie do zmiennej podczas obliczania wartości będzie odnosiło się nie do stanu w momencie deklaracji , ale do stanu w momencie odwołania do . 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 przypisuje zmiennej 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 rozszerzający
język wyrażeń z poprzednich zadań następująco:
Lambda-abstrakcja reprezentuje anonimową (nienazwaną) funkcję jednoargumentową, natomiast wyrażenie to aplikacja do (wyrażenie powinno zatem obliczać się do funkcji). Np.
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 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:
który w stanie pustym (wszystkie zmienne nieokreślone) nie ma wartości przy przekazywaniu parametru przez wartość (bo odwołanie do zmiennej jest niepoprawne) a oblicza się do wartości 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 . 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 , a w dynamicznej do wartości (parametr przekazywany przez wartość):
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 pozważmy program:
Przy przekazywaniu przez wartość, w stanie pustym program się nie obliczy, ponieważ nie da się obliczyć parametru aktualnego . Natomiast przy przekazywaniu przez nazwę, parametr aktualny będzie obliczany dopiero w momencie odwołania do parametru formalnego , czyli w momencie obliczania wartości wyrażenia . W stanie tym zmienna ma już wartość, a zatem wartością całego programu będzie 21.