210 likes | 315 Views
Języki i środowiska programowania systemów rozproszonych. Wykład 11 Procedury, procedury funkcyjne. Wykładowca : Tomasz Kowalski Wykłady przygotowane na podstawie materiałów prof. Kazimierza Subiety. Krótka charakterystyka procedur.
E N D
Języki i środowiska programowania systemów rozproszonych Wykład 11 Procedury, procedury funkcyjne Wykładowca: Tomasz Kowalski Wykłady przygotowane na podstawie materiałów prof. Kazimierza Subiety
Krótka charakterystyka procedur • Imperatywne języki programowania, w tym języki obiektowe, są wyposażone w mechanizmy procedur. • Ich istotą jest to, że: • Hermetyzują dowolnie skomplikowane obliczenia, • Ich wnętrze jest niedostępne z zewnątrz, • Mogą być wywoływane z wielu miejsc, • Ich przystosowanie do konkretnego celu następuje poprzez określenie parametrów lub poprzez efekty uboczne (czyli korzystanie ze stanu spoza danej procedury lub zmiany tego stanu). • Procedury mogą być dalej podzielone na: • Procedury właściwe i procedury funkcyjne (zwane też funkcjami); • Procedury i metody; • Procedury znajdujące się po stronie programu aplikacyjnego i przechowywane w bazie danych. • Procedury właściwe nie zwracają wyniku, nie mogą więc być użyte jako składniki wyrażeń, zaś procedury funkcyjne zwracają wynik i przez to ich wywołania są szczególnymi przypadkami wyrażeń.
Procedury pierwszej i drugiej kategorii program. • Tradycyjnie, procedury po kompilacji są nierozerwalną częścią danego programu aplikacyjnego (są drugiej kategorii programistycznej), • W związku z czym nie można ich podczas czasu wykonania usunąć, wstawić, zmienić, itd. • W systemach BD pojawił się inny typ procedury, zwany zapamiętaną procedurą (stored procedure) lub procedurą bazy danych (database procedure). • Są pierwszej kategorii programistycznej, wiązane dynamicznie. • Można je dynamicznie tworzyć, usuwać lub zmieniać. • Są pisane w specjalnym interpretowanym języku, np. w PL/SQL. • Nie mogą być pisane w klasycznych lub obiektowych językach programowania takich jak C++ lub Java. • Możliwa jest dowolna kombinacja tych opcji. • Np. w systemie Oracle występują zapamiętane metody, czyli kombinacja procedur bazy danych i metod. • Znane z SQL perspektywy (views) można uważać za zapamiętane procedury funkcyjne.
Efekty uboczne procedur/metod • Efekt uboczny jest to działanie procedury na innym środowisku niż własne lokalne środowisko. • Pasywny efekt uboczny – procedura korzysta z innego środowiska. • Aktywny efekt uboczny – procedura dokonuje zmian w zewnętrznym środowisku. • Efekty uboczne są podstawą technik programistycznych. • Projektanci języków nie ograniczają możliwości dostępu do zasobów zewnętrznych, w tym danych globalnych i danych z bazy danych, oraz aktualizacji tych zasobów. • Niestety styl specyfikacji procedur często ignoruje fakt, że mogą one mieć efekty uboczne. • W oryginalnej propozycji Parnasa dotyczącej modułów efekty uboczne były uwzględnione w postaci tzw. list importowych. • Rozwiązanie to zostało zastosowane w języku Modula-2 (następca Pascala). • Brak specyfikacji i kontroli efektów ubocznych w popularnych językach programowania, takich jak C++ i Java jest zwiększaniem skłonności oprogramowania do błędów (katastrofą rakiety Ariane-5 była spowodowany brakiem wyspecyfikowanych efektów ubocznych)
Procedury/metody rekurencyjne • Procedury/metody mogą być rekurencyjne, co implikuje konieczność stosowania dyscypliny w zakresie komunikowania parametrów oraz mechanizmu kontrolującego zakresy obowiązywania nazw użytych w ciałach procedur oraz wiązania tych nazw. • Mechanizmy te są oparte o ten sam stos środowiskowy znany nam z poprzednich wykładów. • Dalej pokażemy w jaki sposób ten stos będzie przystosowany do wspomagania wszelkiego rodzaju procedur (w tym metod). • Będziemy przy tym przyjmować, że zarówno parametry procedur jak i wynik procedur funkcyjnych będą określone poprzez zapytania.
Parametry procedur • Procedury mogą mieć parametry. • W odróżnieniu od funkcji matematycznych, gdzie w zasadzie nie mówi się o sposobach komunikowania parametrów, w językach programowania wykształciło się kilka dobrze rozpoznanych metod komunikowania parametrów o istotnych różnicach semantycznych. • Dalej podamy krótki ich przegląd. • Niektóre z tych metod będą omówione dla przypadku parametrów będących zapytaniami. • Możliwe są nowe metody nie spotykane lub rzadko spotykane w literaturze. • Przykładem jest wołanie, w którym każdy parametr aktualny jest skojarzony z nazwą parametru formalnego. • Umożliwia to przy wywołaniu dowolną kolejność parametrów oraz pomijanie parametrów (zastępowanie ich wartościami domyślnymi).
Wołanie poprzez wartość (call-by-value) • Jest to technika przekazywania argumentu procedury (lub metody) do jej wnętrza, w której przekazywana jest wartość argumentu obliczona przed przekazaniem sterowania do wnętrza procedury. • Jeżeli argumentem jest referencja, to dokonuje się automatycznie dereferencji. • W niektórych językach (C/C++) tworzy się lokalną zmienną zawierającą kopię wartości przekazanej jako argument. • Zmienna ta ma nazwę parametru formalnego i można ją aktualizować tak jak zwykłą lokalną zmienną. • W innych językach (Pascal, Modula-2) taka aktualizacja takiej zmiennej jest zabroniona, gdyż parametr w ciele procedury jest inaczej traktowany niż zmienna.
Wołanie poprzez referencję (call-by-reference) • Technika przekazywania argumentu procedury (lub metody) do jej wnętrza, w której przekazywana jest referencja do obiektu. • Dzięki temu wewnątrz procedury można dokonać zmiany stanu (aktualizacji) obiektu zewnętrznego w stosunku do tej procedury. • Wołanie przez referencję jest kojarzone z własnością zmienności (mutability). • Obiekt przekazywany poprzez referencję jest dostępny w ciele procedury nie pod swoją nazwą, lecz pod nazwą jej odpowiedniego parametru formalnego. • Wołanie przez referencję musi być odróżnione od innych sposobów wołania poprzez składnię. • W językach Pascalo-podobnych jest to słowo kluczowe var. • W innych językach (np. IDL CORBA) są to słowa kluczowe inout oraz out; drugie z nich oznacza, że komunikowany obiekt (zmienna) może nie być zainicjowany, wobec czego nie wolno go czytać. • Dla symetrii, w takich systemach wołanie przez wartość oznacza się słowem kluczowym in.
Ścisłe wołanie przez wartość (strict-call-by-value) • W technice tej nie występuje zróżnicowanie na wołanie przez wartość i wołanie przez referencję. • Jeżeli komunikowana jest wartość, to jest ona przekazywana do ciała procedury tak jak w wołaniu przez wartość. • Jeżeli komunikowana jest referencja (pointer), to bez żadnych zmian przekazywany jest on do wnętrza procedury, tak jak w wołaniu przez referencję. • Przykładem zastosowania tej techniki jest język C.
Wołanie poprzez wartość ze zwrotem (call-by-value-return) • Wołanie poprzez referencję, w którym wewnątrz ciała procedury tworzy się lokalną kopię obiektu (zmiennej), którego referencja jest przekazana jako parametr. • Wszelkie operacje na parametrze wewnątrz ciała procedury następują na tej lokalnej kopii, a nie na oryginalnej zmiennej (różnica z wołaniem przez referencję). • W momencie zakończenia procedury wartość tego lokalnego obiektu podstawia się z powrotem na obiekt, którego referencja jest przekazana jako parametr. • Technika ma duże znaczenie w systemach rozproszonych, gdzie komunikowana referencja może dotyczyć obiektu znajdującego się poza przestrzenią adresową pamięci operacyjnej danego komputera. • Technika nie jest w pełni semantycznie „czysta”, gdyż umożliwia nałożenie się dwóch lub więcej aktualizacji w ramach tego samego programu.
Wołanie poprzez nazwę (call-by-name) • Technika przekazywania parametrów (po raz pierwszy zastosowana w języku Algol-60) polegająca na tym, że parametru nie oblicza się w momencie wołania metody/procedury, ale przekazuje się go w postaci kodu (tekstu) wyrażenia będącego parametrem aktualnym. • Tekst ten zastępuje wszystkie wystąpienia parametru formalnego w ciele metody/procedury (parametr jest traktowany jak makro). • Specyfiką tej techniki jest to, że środowisko, w którym taki parametr jest ewaluowany, jest środowiskiem wywołania tej metody/procedury, a nie jej środowiskiem lokalnym (co jest konieczne z semantycznego punktu widzenia); • Technika nie jest semantycznie „czysta” - może powodować efekt, w którym obliczane wartości parametru w poszczególnych miejscach ciała metody/procedury nie są identyczne ze względu na zmianę stanu. • Technika ta jest powszechnie stosowana w przypadku parametrów makr. • Może mieć również znaczenie dla technik optymalizacyjnych (optymalizacji zapytań) opartych na przepisywaniu (rewriting).
Wołanie poprzez potrzebę (call-by-need) • Technika przekazywania parametrów określana także jako leniwa ewaluacja (lazy evaluation). • Oznacza opóźnienie wyliczania wartości parametru aktualnego do momentu, kiedy będzie on rzeczywiście potrzebny wewnątrz ciała metody/procedury. • Parametr jest ewaluowany tylko raz (różnica z wołaniem przez nazwę), w środowisku wywołania metody/procedury. • Technika ma na celu optymalizację czasu wykonywania i może być kombinowana z innymi metodami (call-by-value, call-by-reference, itd.). • Podobnie do wołania przez nazwę, technika ta nie jest semantycznie „czysta”, gdyż może prowadzić do efektu, w którym wartość wyliczonego parametru jest inna niż wartość, która byłaby wyliczona w momencie wołania metody/procedury.
Procedury w SBQL • Przyjmiemy następującą składnię deklaracji procedury (nie uwzględniającą kontroli typologicznej): • Wywołanie procedury: procedura ::= procedure nazwaProc {instrukcje} procedura ::= procedure nazwaProc( ) {instrukcje} procedura ::= procedure nazwaProc ( parFormalne) {instrukcje} nazwaProc ::= nazwa parFormalne ::= parFormalny | parFormalny; parFormalne parFormalny ::= nazwa | in nazwa | out nazwa instrukcja ::= return [zapytanie] instrukcja ::= nazwaProc | nazwaProc( ) | nazwaProc ( parAktualne ) zapytanie ::= nazwaProc | nazwaProc( ) | nazwaProc ( parAktualne ) parAktualne ::= parAktualny | parAktualny ; parAktualne parAktualny ::= zapytanie
Semantyka procedur (1) • Nie rozróżniamy procedur właściwych i procedur funkcyjnych. • Procedura funkcyjna musi zawierać wewnątrz instrukcję return, z parametrem będącym zapytaniem. • Instrukcja ta kończy działanie procedury. • Taka procedura może być wywołana jako zapytanie. • Jeżeli procedura funkcyjna jest wywołana jako instrukcja (poza zapytaniem), wówczas zwracany przez nią wynik jest ignorowany. • Jeżeli napotkana instrukcja return nie ma parametru w postaci zapytania, to jej wykonanie kończy działanie procedury, ale nic ona nie zwraca (wobec czego nie jest procedurą funkcyjną). • Przyjęliśmy, że formalne parametry procedury mogą być pozbawione kwalifikatora; wówczas oznacza to ścisłe wołanie przez wartość (strict-call-by-value). • Kwalifikator in oznacza zwykłe wołanie przez wartość, zaś kwalifikator out oznacza wołanie przez referencję.
Semantyka procedur (2) • Istotne jest jak i gdzie procedura będzie zapamiętana. • Najczęstszym rozwiązaniem jest uwzględnienie deklaracji procedury w środowisku rozwoju oprogramowania. • Miejsce ulokowania procedury wynika wtedy z innych czynników, np. miejscem jest moduł lub klasa, jeżeli dana procedura jest składową kodu źródłowego tego modułu lub klasy. • W przypadku baz danych procedury są bytami pierwszej kategorii programistycznej, wobec tego mogą być potrzebne specjalne udogodnienia administracyjne dla ich tworzenia, wstawiania w odpowiednie miejsce składu obiektów, kompilowania, usuwania, zabezpieczania, optymalizacji, itd. • W systemie Loqis przyjęliśmy, że procedury są składowymi modułów źródłowych lub klas, które po kompilacji stają się modułami lub klasami bazy danych. • Procedury takie mogą być przenoszone pomiędzy modułami lub klasami poprzez instrukcję insert.
Gzie procedurę można zapamiętać? • Procedurę można zapamiętać w dowolnym środowisku składu obiektów, w szczególności: • W bazie danych po stronie serwera na najwyższym poziomie hierarchii obiektów. • W środowisku lokalnym sesji użytkownika, po stronie klienta, czyli programu aplikacyjnego i środowiska jego wykonania. • Wewnątrz dowolnego obiektu, w szczególności, modułu bazy danych, o ile takie pojęcie będzie wprowadzone. • Wewnątrz klasy, zarówno umieszczonej po stronie serwera bazy danych, jak i po stronie aplikacji klienta. Umieszczenie procedury wewnątrz klasy powoduje, że staje się ona tym, co powszechnie jest określane w obiektowości jako „metoda”. • Wewnątrz specjalnej biblioteki procedur po stronie serwera, lub pewnej struktury takich bibliotek. • Zależnie od miejsca umieszczenia procedur w składzie, bindery zawierające referencje do procedur i ich nazwy muszą być umieszczone w odpowiednich sekcjach stosu ENVS. • W ten sposób nazwy procedur będą dostępne do wiązania.
Semantyka wywołania procedur (1) • Najpierw wiąże się jej nazwę występującą w zapytaniu/programie na stosie ENVS. Wynikiem jest referencja do procedury. • Następuje uruchomienie procedury, czego skutkiem jest pojawienie się na stosie ENVS sekcji z binderami (zwanej zapisem aktywacyjnym). • Zapis aktywacyjny zawiera trzy rodzaje bytów: • bindery aktualnych parametrów procedury; • bindery do lokalnych obiektów procedury; • ślad powrotu, umożliwiający przekazanie sterowania do kodu wywołującego procedurę po zakończeniu jej działania. Ślad ten w tym kroku jest wstawiany do zapisu aktywacyjnego. • Jeżeli procedura była umieszczona wewnątrz klasy (tj. była metodą), to poniżej zapisu aktywacyjnego umieszcza się sekcję z binderami do wszystkich prywatnych własności tej klasy. • Następuje ewaluacja zapytań będących parametrami procedury; wynik tej ewaluacji znajduje się na QRES. • Po ewaluacji parametrów tworzy się z nich bindery i wstawia do sekcji ENVS zawierającej zapis aktywacyjny procedury. Parametry te usuwa się z QRES.
Semantyka wywołania procedur (2) • Jeżeli procedura miała zadeklarowane obiekty, to są one tworzone w składzie, zaś ich bindery są umieszczone wewnątrz zapisu aktywacyjnego. • Sterowanie jest przekazywane do ciała procedury. • Przy wiązaniu nazw wewnątrz ciała procedury wszystkie sekcje procedur, które wywołały bezpośrednio lub pośrednio daną procedurę (wraz z towarzyszącymi im sekcjami obiektów, zapytań, klas, itd.) są niedostępne do wiązania. • Jeżeli sterowanie osiągnie końcowy nawias ciała procedury, lub napotka instrukcję return, to procedura kończy swoje działanie. • Jeżeli instrukcja return miała parametr w postaci zapytania, to ewaluuje się je w środowisku tej procedury, jak zwykle. • Wynik, jak zwykle, znajdzie się na wierzchołku QRES jako wynik działania procedury funkcyjnej. • Zakończenie działania procedury oznacza zdjęcie ze stosu wszystkich sekcji, które były tam włożone w momencie jej startu, usunięcie zadeklarowanych lub utworzonych lokalnych obiektów, oraz powrót sterowania do programu wywołującego, zgodnie ze śladem powrotu.
Przykład procedury (1) • Procedura ZmieńDział zmienia dział dla pracowników komunikowanych jako parametr P na dział komunikowany jako parametr D. • Wywołanie procedury: przenieś wszystkich analityków do działu kierowanego przez Nowaka: • Jeżeli system respektowałby automatyczną aktualizację bliźniaczych pointerów (PracujeW i Zatrudnia), to procedura zostałaby uproszczona: procedure ZmieńDział( P; out D ) { delete Dział.Zatrudnia where Prac P; for each P as p do { p.PracujeW := ref D insert D, createref p as Zatrudnia }} ZmieńDział( Prac where Stan = ”analityk”; Dział where (Szef.Prac.Nazwisko) = ”Nowak” ) procedure ZmieńDział( P; out D ) { for each P doPracujeW := ref D }}
Przykład procedury (2) • Procedura funkcyjna MałoZarabiający zwraca nazwisko, zarobek i nazwę działu dla pracowników określonych stanowisk zarabiających mniej niż średnia. Wynik jest strukturą z nazwami N, Z, D. • Podaj nazwiska i zarobek dla mało zarabiających piekarzy i stolarzy z działu produkcji. • Podwyższ o 100 zarobek wszystkim mało zarabiającym programistom z działu konserwacji. procedure MałoZarabiający ( in Stanowiska ) { create avg( Prac.Zar ) as Średnia; create ref (Prac where Stan Stanowiska and Zar < Średnia) as Mało; return Mało.Prac.( Nazwisko as N, Zar as Z, (PracujeW.Dział.Nazwa) as D) }; (MałoZarabiający( bag(”piekarz”,”stolarz”)) where D = ”produkcja”). (N, Z) for each MałoZarabiający( ”programista”) ) where D = ”Konserwacja” do Z := Z+100;
Procedury funkcyjne a perspektywy • Procedura BogatyPrac zwraca informacje o pracownikach, który zarabiają brutto co najmniej 3000. Informacja zawiera nazwisko pracownika jako Nazwisko, nazwisko jego szefa jako Szef oraz zarobek netto jako Zarobek. procedureBogatyPrac { return (PracwhereZar 3000). ( NazwiskoasNazwisko, (PracujeW.Dział.Szef.Prac.Nazwisko) asSzef, ZarNetto() asZarobek) }; • Nazwiska i zarobki netto bogatych pracowników pracujących dla Wilickiego: (BogatyPracwhereSzef = „Wilicki”) . (Nazwisko, Zarobek) • Procedura BogatyPrac została użyta w taki sposób, że użytkownik może rozumieć nazwę BogatyPrac jako nazwę obiektów posiadających trzy atrybuty: Nazwisko, Szef i Zarobek. • Są to obiekty „wirtualne”, istniejące w postaci definicji (i w wyobraźni programisty), ale nieobecne w składzie danych. • Procedura BogatyPrac przypomina więc pojęcie z baz danych znane jako perspektywa (view).