1 / 24

Typy w językach i systemach obiektowych (i nie tylko) Cześć 1.

Typy w językach i systemach obiektowych (i nie tylko) Cześć 1. Kazimierz Subieta Instytut Podstaw Informatyki PAN, Warszawa subieta@ipipan.waw.pl http://www.ipipan.waw.pl/~subieta Polsko-Japońska Wyższa Szkoła Technik Komputerowych, Warszawa. Co to jest „typ”?.

Download Presentation

Typy w językach i systemach obiektowych (i nie tylko) Cześć 1.

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Typy w językach i systemach obiektowych(i nie tylko)Cześć 1. Kazimierz Subieta Instytut Podstaw Informatyki PAN, Warszawa subieta@ipipan.waw.pl http://www.ipipan.waw.pl/~subieta Polsko-Japońska Wyższa Szkoła Technik Komputerowych, Warszawa

  2. Co to jest „typ”? Typ jest wyrażeniem językowym lub pewną strukturą semantyczną przypisaną do zmiennej, wyrażenia lub innego bytu programistycznego: danej, obiektu, funkcji, procedury, operacji, metody, parametru procedury, modułu, ADT, wyjątku, zdarzenia, ... Typ specyfikuje: rodzaj wartości, które może przybierać ten byt (w przypadku np. zmiennej, obiektu lub parametru) lub “zewnętrzne” cechy (interfejs) np. dla procedury, funkcji, operacji lub metody. Z jednej strony typ jest formalnym ograniczeniem narzuconym na budowę bytu programistycznego (lub jego specyfikację parametrów i wyniku). Z drugiej strony typ jest formalnym ograniczeniem kontekstu, w którym odwołanie do tego bytu może być użyte w programie.

  3. Po co są typy? Zasadniczym celem typów jest wspomaganie kontroli poprawności programu. W językach z tzw. mocnym typowaniem (strongtyping), np. Pascalu i Modula-2, każdy deklarowany byt programistyczny musi być obowiązkowo wyposażony w deklarację typu. Poprzez tę deklarację programista wyraża swoje oczekiwania co do roli tego bytu w programie. Te oczekiwania są następnie sprawdzane we wszystkich tych miejscach programu, gdzie występuje odwołanie do tego bytu. Np. określając typ zmiennej X jako integer programista ustala, że ta zmienna ma przechowywać wartości całkowite. Dzięki temu możliwe jest sprawdzenie, czy wszystkie odwołania do tej zmiennej w programie mają kontekst, w jakim może być użyta wartość całkowita. Mocna typologiczna kontrola poprawności programów okazała się cechą skutecznie eliminującą błędy popełniane przez programistów. Według typowych szacunków, po wyeliminowaniu błędów syntaktycznych programu około 80% pozostałych błędów jest wychwytywane przez mocną kontrolę typu. Trudności implementacyjne, dodatkowe obciążenie czasu programisty, długości kodu, zasobów komputera, jak również częsta utrata niezbędnej elastyczności powodują, że w wielu produktach komercyjnych taka kontrola jest zaniedbywana lub występuje w postaci szczątkowej (Smalltalk, SQL).

  4. Inne role przypisywane typom Oprócz zasadniczej roli kontrolnej, typom przypisuje się kilka innych: Określenie reprezentacji wartości wewnątrz pamięci komputera, rozmiaru danej i jej formatu. Np. typ integer oznacza, że dana będzie przechowywana na dwóch bajtach w kodzie uzupełnieniowym. Struktura zapisów lub obiektów (włączając w to nazwy atrybutów) Pośredniczenie w mechanizmach wiązania (binding). Dzięki typom możliwa jest znacznie bardziej efektywna reprezentacja wartości, jak również wiązanie nazw może odbywać się w czasie kompilacji. Ta własność umożliwia znaczną poprawę czasów wykonania programów. Modelowanie pojęciowe: nazwa typu pewnej danej niesie informację o jej zewnętrznym znaczeniu (semantyce). Np. typ Data lub Osoba posiada konotacje w świecie zewnętrznym poprawiając czytelność programów.

  5. Typ a obiektowość Pojęcie typu jest w gruncie rzeczy ortogonalne w stosunku do obiektowości. Popularny język obiektowych Smalltalk nie ma typów i ma bardzo ograniczoną zdolność kontroli poprawności w zakresie typów (tzw. kontrola dynamiczna). W wielu opracowaniach i językach (Java, C++, Eiffel) pojęcie typu jest podstawową cechą obiektowości ze względu na powiązanie go z klasą. Typ i klasa oznaczają to samo; nazwa klasy jest jednocześnie używana jako nazwa typu. Utożsamienie pojęć klasy i typu jest generalnie nieporozumieniem. Typ obiektu jest jednym z inwariantów przechowywanym w ramach klasy (niekoniecznie obligatoryjnym). Klasa może zawierać wiele innych inwariantów, a także innych cech (takich jak np. ograniczenia, reakcje na wyjątki, itd.), które są całkowicie nierelewantne do pojęcia typu. W innych opracowaniach klasę uważa się za definicję „obiektów”, zaś typy za definicję „wartości”. Tego rodzaju rozróżnienie jest również kontrowersyjne.

  6. Czy klasa jest “implementacją” typu? Często proponuje się rozróżnienie typu i klasy na zasadzie: „klasa jest implementacją typu”. W tym rozumieniu typ jest odpowiedzialny za widzialną z zewnątrz specyfikację klasy, zaś sama klasa zawiera w środku całą implementacyjną kuchnię, włącznie z kodami metod. Taką definicję można uważać za poprawną dla wielu koncepcji obiektowych i języków; niestety, modele obiektowe przyjęte w większości obiektowych systemów zarządzania bazami danych (oraz standard ODMG) wykraczają poza wymieniony związek pomiędzy typem i klasą. Przykładowo, w przypadku wielo-dziedziczenia typ obiektu jest „sklejony” z typów dwóch lub więcej klas; co w takim razie jest jego “implementacją”? Typy mogą być także definiowane rekurencyjnie, podczas gdy tego rodzaju definicje są nieprzydatne dla klas. Z traktowaniem klasy jako implementacji typu kłócą się także modele zakładające, że jeden obiekt ma jednocześnie wiele typów, zgodnie z hierarchią dziedziczenia (np. standard ODMG).

  7. Typy masowe Typami masowymi (bulk types) są nazywane takie typy, dla których rozmiar wartości nie da się przewidzieć ani sensownie ograniczyć. Do typów masowych zaliczane są wszelkie zbiory, relacje, wielozbiory, sekwencje, listy, drzewa, itp. Typy masowe są nieobecne w wielu popularnych językach programowania, np. w C. W Pascalu są one zredukowane do jednego pojęcia pliku (file), dodatkowo obciążonego licznymi ograniczeniami natury implementacyjnej. Brak typów masowych oznacza, że programista musi dość często używać pojęcia sterty (heap) do implementacji różnych zadań, co związane jest z dość uciążliwymi operacjami oraz znacznie zwiększoną skłonnością do błędów (np. wyciekanie pamięci). Popularne języki obiektowe nie mają typów masowych lub wprowadzają różnorodne ograniczenia na ich strukturę lub środki dostępu i użycia (Smalltalk, C++, Java).

  8. Typy masowe w bazach danych Bazy danych są przede wszystkim nastawione na przetwarzanie typów masowych. Przykładowo, do takich typów należy relacja. Typy masowe w bazach danych spowodowały powstanie nowego rodzaju interfejsu programistycznego, zwanego językiem zapytań. Najwybitniejszym przykładem języka zapytań jest SQL. Języki zapytań nie są zwykle dostatecznie uniwersalne, skąd wynika konieczność połączenia języka zapytań (SQL) z uniwersalnymi językami programowania. Z kolei brak (lub ograniczenia) typów masowych w językach programowania jest główną przyczyną niekorzystnego efektu określanego jako niezgodność impedancji (impedance mismatch). Obiektowe bazy danych, w założeniu, miały unikać tego efektu poprzez odpowiednie potraktowanie typów masowych zarówno w bazach danych jak i w językach programowania. Niestety, w obecnych systemach obiektowych baz danych (w szczególności bazujących na standardzie ODMG) to początkowe założenie pozostało w sferze pobożnych życzeń, głównie z powodu oparcia ich interfejsów na C++, Java i Smaltalk’u.

  9. Kolekcje w ODMG • W terminologii ODMG typy masowe noszą nazwę „kolekcje”. Wśród nich wyróżniono następujące: Zbiory (sets): mogą one zawierać elementy dowolnego, ustalonego typu, ale bez powtórzeń. Wielozbiory(multisets, bags): podobnie jak zbiory, ale elementy mogą się powtarzać. Sekwencje(sequences): uporządkowane kolekcje elementów dowolnego, ustalonego typu; taki porządek ma znaczenie informacyjne, zaś elementy mogą się powtarzać. Dostęp do takich elementów jest również sekwencyjny. Możliwy jest jednak dostęp poprzez indeks (kolejny numer) elementu sekwencji, tak jak dla dynamicznych tablic. Tablice dynamiczne(dynamic arrays): tak jak sekwencje, ale z dostępem tak jak do tablic (poprzez indeks wiersza tablicy). Słowniki, czyli zbiory par (t,v), gdzie t jest pewnym hasłem (ciagiem znaków), zaś v jest wartością dowolnego typu. Intencją wprowadzenia tego typu jest szybki dostęp do jego elementu kolekcji na podstawie znajomości wartości t. Powyższy zestaw jest nieco kontrowersyjny ze względu na koncepcyjną redundancję. Można go bez żadnej szkody zredukować do dwóch elementów: wielozbiory i sekwencje.

  10. Zasada ortogonalnego zagnieżdzania typów Postulatem, który zdobywa sobie coraz większą popularność, jest ustrukturalizowanie języka typów poprzez wyróżnienie w nim typów i konstruktorów typów, takich jak struktury, zbiory, sekwencje, tablice, itd. Postulowana jest zasada ortogonalności konstruktorów typów. Oznacza ona, że typy można dowolnie zagnieżdżać. Każdy typ może być parametrem każdego konstruktora typów (z dokładnością do rozsądnych ograniczeń, np. nie widać sensu deklarowania zbioru procedur). Dotyczy to także typów masowych. Możliwe jest więc tworzenie np. zbiorów sekwencji obiektów, obiektów z atrybutami w postaci wielozbiorów, itp.

  11. Przykład zagnieżdżania typów Pracownicy ..... Pracownik Pracownik Zatrudnienia Zatrudnienia Nazwisko Nazwisko Zatrudnienie Zatrudnienie Zatrudnienie Zatrudnienie Stanowisko Stanowisko Dzieci Dzieci ..... ..... ... ... Dziecko Dziecko Dziecko Dziecko Kombinacja typów masowych Pracownicy są typem masowym, zawierającym obiekty Pracownik, które zawierają atrybut Zatrudnienia będąca sekwencją miejsc jego wcześniejszego zatrudnienia, oraz zbiór Dzieci zawierający obiekty Dziecko.

  12. Zasada zagnieżdżania a model relacyjny Model relacyjny nie zakładał i wręcz zwalczał ortogonalność konstruktorów typów jak szkodliwą dla prostoty systemu. Przykładowo, w modelu relacyjnym nie był dopuszczalny atrybut będący zbiorem wartości; było to naruszenie tzw. pierwszej formy normalnej. Wydaje się jednak, że zwolennicy tego rodzaju doktrynalnych ograniczeń ortogonalności ostatecznie przegrali (przynajmniej w sensie ideologicznym), ponieważ okazały się one wadliwe zarówno z punktu widzenia modelowania pojęciowego, jak i niezbędnej elastyczności w zakresie struktur danych. Jednocześnie, ich zalety implementacyjne oraz wspomaganie ze strony zmatematyzowanych teorii okazały się iluzją. Model zagnieżdżonych relacji (określany jak NF2) - poczatki wyłomu w tej ideologii. Obiektowość dokonała reszty.

  13. Rożny stosunek do typów Program Języki programowania: Typ Typ Typ Dana Dana Dana Dana Dana Dana Dana Program Bazy danych Dana Program Dana Program Schemat Typ?

  14. Przesunięcie typów na stronę danych Dana Program Dana Program Dana Program Typ Typ Typ Typ Schemat Dana Dana Dana Programy są częściowo “zwalniane” z obowiazku trzymania typów. Typy są bezpośrednio powiązane z danymi w bazie danych i przyjmują formę niezależną od języka programowania (OMG CORBA, ODMG).

  15. Podtyp Istnieją dwie nieco różne definicje typu podrzędnego (podtypu). Pierwsza z nich opiera się na koncepcji zawierania się zbiorów: T1 jest podtypem T2, jeżeli zbiór wartości definiowany przez T1 zawiera się w zbiorze wartości definiowanym przez T2. Np. typ liczba naturalna jest podtypem typu liczba całkowita. Druga, ważniejsza definicja ustala inną zależność. Typ T1 jest podtypem typu T2, jeżeli zawiera więcej atrybutów (operacji, metod, itp.) niż typ T2. Przykładowo, typ typedef TypPrac = struct { stringNazwisko, intRok_urodz, intZarobek } jest podtypem typu typedef TypOsoba = struct { stringNazwisko, intRok_urodz } Taka definicja podtypu ma być w założeniu zgodna z hierarchią klas: pierwszy typ może dotyczyć obiektów Pracownik, zaś drugi obiektów Osoba; klasa Pracownik jest podklasą klasy Osoba.

  16. Zasada zastępowania substitutability W każdym miejscu programu gdzie może być użyty pewien typ T może być także użyty dowolny podtyp typu T. Przykładowo, wszędzie tam, gdzie można użyć liczby całkowitej, można także użyć liczby naturalnej. Wszędzie tam, gdzie można użyć obiektu Osoba można także użyć obiektu Pracownik. Ponieważ podtyp zawiera więcej atrybutów niż nadtyp, zasada zastępowania oznacza ignorowanie wszystkich tych atrybutów, które „wystają” poza typ oczekiwany w danym miejscu programu. TypOsobaos, os1; TypPracpr; //Deklaracje zmiennych os, os1, pr os = (Nazwisko: „Nowak”, Rok_urodz: 1960); pr = (Nazwisko: „Starszak”, Rok_urodz: 1950, Zarobek: 3000); os1:= os; os1:= pr; //Zastosowanie zasady zastępowania

  17. Bardziej złożona definicja podtypu typedef TypMojegoPrac = struct { dataod_kiedy, TypPrac osoba, data do_kiedy } jest podtypem typu typedef TypMojejOsoby = struct { dataod_kiedy, TypOsoba osoba } TypMojegoPrac nie tylko zawiera więcej atrybutów, ale posiada atrybut osoba, którego typ jest podtypem typu osoba w TypMojejOsoby . Tego rodzaju przypadek uogólnia się na dowolną podobną sytuację na podstawie dość naturalnej rekurencyjnej definicji.

  18. Typ procedury, funkcji, metody • Typ ten jest czesto nazywany “sygnaturą”. • Sygnatura jest wyrażeniem językowym określającym: • nazwę metody, operacji lub funkcji • liczbę, nazwy i typy jej parametrów • typ zwracanego przez nią wyniku • wyjątki, które mogą być przez nią spowodowane nazwa metody nazwa par. nazwa par. nazwa par. typ wyniku typ par. typ par. typ par. short wystaw_ocenę(inshort jaką, in Wykład z_czego,out Student komu) raises(student_nie_był_zapisany); wyjątek Sygnatura umożliwia oddzielenie specyfikacji od implementacji. Przy specyfikacji metody, operacji lub funkcji podaje się tylko jej sygnaturę.

  19. Problem podtypowania dla metod Przy realizacji zasady zastępowania, czyli po przejściu na poziom klasy bardziej ogólnej, pewna metoda może zmienić swoją implementację. Z punktu widzenia kontroli typów istotne jest jednak to, w jaki sposób może ona zmienić swoją specyfikację. Załóżmy, że w ramach klas Pracownik i Osoba mamy następujące specyfikacje typu: typedef TypPrac = struct { stringNazwisko, intRok_urodz, int SumaZarobkow( data1do_kiedy ),... } typedef TypOsoba = struct { stringNazwisko, intRok_urodz, int SumaZarobkow( data2 do_kiedy ) } Przyjmimy dla ustalenia uwagi, że definicje typów data1 i data2 mogą być następujące: typedefdata1 = struct{ int rok, int miesiac} typedefdata2 = struct{ int rok, int miesiac, int dzien} Należy podjąć decyzję, w jakim stosunku powinny pozostać typy data1 oraz data2, aby TypPrac był podtypem typu TypOsoba, zaś zasada zastępowania była spełniona.

  20. Inwariancja, kowariancja, kontrawariancja Załóżmy, że metoda m(T A), parametryzowana parametrem A typu T, jest zdefiniowana w klasie K. Załóżmy, że klasa K posiada specjalizację (podklasę) K’, w której jest zdefiniowana metoda m(T' B), parametryzowana parametrem B typu T’; nowa metoda przesłania poprzednią. Powstaje pytanie, jaki ma być stosunek pomiędzy typami T i T’? Istnieją trzy możliwości: (1) inwariancja (C++) oznaczająca, że T musi być identyczny z T’; (2) kowariancja (Eiffel) oznaczająca, że T’ musi być podtypem T (w szczególności, T=T’); (3) kontrawariancja (Sather) oznaczająca, że T’ musi być nadtypem T (w szczególności, T=T’). Zasady inwariancji, kowariancji lub kontrawariancji są dołączone jako meta-reguły do reguł wnioskowania o poprawności typologicznej. Podany na poprzednim slajdzie przykład będzie poprawny dla Sather. Zwolennicy kowariancji uważają, że kontrawariancja jest nienaturalna dla programistów, wobec czego prowadzi do błędów i nieporozumień. Zwolennicy kontrawariancji argumentują, że kowariancja oznacza zmniejszenie skuteczności mocnej statycznej kontroli typów. Wobec różnych kryteriów oceny dyskusje nie prowadzą do jednoznacznych konkluzji.

  21. Polimorfizm w zakresie typów Polimorfizm (słowo pochodzenia greckiego) oznacza „wiele form”. W literaturze obiektowej to słowo jest używane w dwóch nieco różnych znaczeniach: Polimorfizm metod: ta sama nazwa operacji może mieć różne znaczenie, w zależności od tego, do którego obiektu został wysłany komunikat zawierający tę nazwę. Polimorfizm metod jest uwarunkowany późnym wiązaniem. Przyjmuje on wiele różnorodnych form. Polimorfizm typów: oznacza istnienie procedur lub funkcji, które mogą działać na wartościach wielu typów i zwracać wartości wielu typów. Przykładowo, funkcja daj_pierwszy(lista) zwraca pierwszy element dowolnej listy, niezależnie od tego, czy jest to lista liczb całkowitych, czy lista liczb rzeczywistych, czy lista zapisów, czy też inna. Głównym motywem polimorfizmu typów jest umożliwienie jednocześnie skutecznej (statycznej) kontroli typologicznej oraz umożliwienie programowania ogólnego (generic). Programowanie ogólne jest problemem w takich językach jak Pascal, gdyż utrudnia je zbyt mocny system kontroli typów. Jest możliwe po przejściu na inny poziom językowy (dynamicznych alokacji), ale to z kolei powoduje wyeliminowanie kontroli typów.

  22. Rodzaje polimorfizmu typów Polimorfizm parametryczny albo nieograniczony (unbounded): typ może być parametryzowany jakimś innym typem. Np. parametr funkcji daj_liczność_zbioru będący zbiorem elementów jest parametryzowany typem elementu zbioru. Polimorfizm inkluzyjny (inclusion) albo ograniczony (bounded): wprowadza się hierarchię typów, uwzględniającą związki generalizacji/specjalizacji. Jeżeli jakaś funkcja lub operacja działa na typie A, to działa również na dowolnym typie B będącym specjalizacją typu A. Np. A jest typem osoba, B jest typem student (zamienność). Ten rodzaj polimorfizmu realizowany jest w językach i systemach obiektowych. Polimorfizm ad hoc: dość przypadkowe formy polimorfizmu implementowane w różnych językach programowania. Np. jeżeli jakaś funkcja działa na real, to działa również na integer. Rozróżnia się dwie formy polimorfizmu ad hoc: przeciążanie i koercje. Przeciążanie jest w gruncie rzeczy wprowadzeniem homonimii na poziomie syntaktycznym: w zależności od kontekstu pewna nazwa lub symbol oznacza co innego. Koercje są funkcjami pozwalającymi programiście explicite zmienić typ obiektu programistycznego w ramach jakiegoś fragmentu programu; przykładem jest operator cast z języków C i C++. Niekiedy koercję kojarzy się z sytuacją, kiedy programista jest zwolniony z obowiązku pisania explicite takiej funkcji, pozostawiając kwestię określenia typu kompilatorowi lub (niekiedy) mechanizmowi czasu wykonania.

  23. Polimorfizm parametryczny - przykład Napier88 (pochodna ML): type Para [T] is record(pierwszy: T, ostatni: T) Deklaracja nieskończonego zestawu typów zapisów posiadających dwa pola, oba typu T. type ParaInt is Para[int] type ParaReal is Para[real] var MojaParaReal : ParaReal MojaParaReal.pierwszy := 3.14 type DateStampedValue[T] is record (value : T, date : Date) type DateStampedImage is DateStampedValue[image]

  24. Polimorfizm typów - komentarz Teoria typów polimorficznych jest zaawansowaną dziedziną. Polimorfizm typów jest niekiedy krytykowany jako przesadny nacisk badań akademickiego na cechy systemu statycznej kontroli typów, owocujący składnią i pojęciami trudnymi do zaakceptowania przez przeciętnego programistę. Z tego powodu niektórzy specjaliści nie wróżą językom polimorficznym powodzenia w świecie komercyjnym. Dla odmiany, specjaliści z kręgów akademickich uważają języki polimorficzne, w szczególności ML i pochodne, za „języki XXI wieku”. Znaczenie tych języków może być bardziej docenione w przypadku radykalnego zwiększenia wymagań na niezawodność oprogramowania. Niemniej trudności implementacyjne, skomplikowany bagaż pojęciowy, bezwładność rynku, który jest niechętny nowym językom, powodują osłabienie ich szans. Z drugiej strony, polimorfizm typów nie jest dostatecznie uniwersalny dla celów programowania generycznego i ustępuje technikom refleksji (reflection) lub szablonom (templates). Poprzez zmieszanie poziomu syntaktycznego z semantycznym techniki te prowadzą do trudności z kontrolą typu. Dodatkowymi czynnikami zmniejszającymi szanse polimorficznych systemów typów są nowe techniki programistyczne, takie jak programowanie wizyjne i zdarzeniowe, języki zapytań, procedury zapamiętane w bazie danych, procedury wiązane dynamicznie, perspektywy, aktywne reguły, techniki komponentowe, wymagające dynamizmu nieakceptowalnego dla mocnej kontroli typów. Jeszcze innym aspektem jest konieczność przetwarzania pół-strukturalnych, heterogenicznych informacji, dla których system typów musi być luźny.

More Related