Typy pochodne 1 Często dogodnie jest wprowadzić nowy typ, który jest podobny do istniejącego, niemniej jednak różny. Niech T będzie pewnym typem. Możemy napisać: type S is new T; W takim przypadku mówimy, że S jest typem pochodnym (derived type) typu T, który nazywamy typem macierzystym (parent type). Mówimy czasami, że S należy do tej samej klasy (class) co typ T.
Typy pochodne 2 Jeżeli T jest typem rekordowym, to typ S jest też typem rekordowym, a jego składowe mają te same identyfikatory. Zbiór wartości typu pochodnego jest kopią zbioru wartości typu macierzystego. Oznacza to, że są to różne typy i nie można wartości jednego typu podstawiać do obiektów drugiego typu. Konwersja jest jednak możliwa. Zapis literałów i agregatów typu pochodnego jest taki taki sam, oraz domyślne wyrażenia początkowe typu, albo jego składowych są takie same jak w przypadku typu macierzystego.
Typy pochodne 3 Definicja Do operacji podstawowych typu zaliczamy: 1.Zdefiniowane wstępnie operacje podstawienia, zdefiniowana wstępnie relacja równości, odpowiednie atrybuty. 2.W przypadku typu pochodnego, operacjami podstawowymi są operacje podstawowe odziedziczone po typie macierzystym, które mogą być ponownie zdefiniowane. 3.W przypadku typu zadeklarowanego w pakiecie definicyjnym, podprogramy zadeklarowane w tym pakiecie, posiadające parametry formalne, lub wynik tego typu.
Typy pochodne 4 Wartości typu wyliczeniowego są także operacjami podstawowymi, ponieważ są traktowane jak identyfikatory funkcji bezparametrowych o wartościach typu wyliczeniowego. Przykład W przypadku typu Boolean, literały False i True są operacjami podstawowymi, ponieważ są traktowane jakby były funkcjami, takimi jak: function True return Boolean is begin return BooleanVal(1); end;
Typy pochodne 5 Ogólna idea jest taka, że typ pochodny posiada pewne operacje podstawowe, dziedziczone po typie macierzystym i można do zbioru tych operacji dodać nowe operacje podstawowe. Operacje dziedziczone mogą być zastąpione przez nowe operacje. Rozumiemy przez to zastąpienie operacji dziedziczonych przez jawnie zadeklarowane podprogramy o tych samych identyfikatorach jak podprogramy należące do zbioru operacji podstawowych typu macierzystego, przy czym podprogramy zastępujące muszą być zadeklarowane w tym samym obszarze deklaracji, w którym definiowany jest typ pochodny.
Typy pochodne 6 Przykład Wektory_Na_Plaszczyznie. Test_Wektory_Na_Plaszczyznie. Deklarując typ pochodny należy przestrzegać dwóch zasad: 1.Nie można tworzyć typu pochodnego z typu prywatnego przed podaniem pełnej deklaracji tego typu. 2.Jeżeli tworzymy typ pochodny w tym samym pakiecie definicyjnym, w którym deklarujemy typ macierzysty, to typ pochodny dziedziczy wszystkie operacje po typie macierzystym i nie można dodać nowych operacji do typu macierzystego po deklaracji typu pochodnego.
Typy pochodne 7 Mimo że, każdy typ pochodny jest różny, to dzięki pokrewieństwu typów pochodnych, wyprowadzonych od wspólnego przodka, wartość jednego typu pochodnego można łatwo zamienić na wartość innego typu powstałej klasy. Przykład Niech będą dane deklaracje: type Light is new Colour; type Signal is new Colour; type Flare is new Signal; Typy te tworzą hierarchię typów, która zaczyna się od typu Colour. Możemy swobodnie dokonywać konwersji wartości tych typów. Na przykład możemy pisać:
Typy pochodne 8 L : Light; F : Flare;... F := Flare(L); zamiast F := Flare(Signal(Colour(L))); Podstawową zaletą wprowadzania typów pochodnych jest unikanie mieszania obiektów, koncepcyjnie należących do różnych typów. Przykład Załóżmy, że chcemy liczyć jabłka i pomarańcze. W tym celu możemy napisać
Typy pochodne 9 type Apples is new Integer; type Oranges is new Integer;... No_Of_Apples : Apples; No_Of_Oranges : Oranges; Obydwa typy pochodzą od typu Integer, dzięki czemu obydwa dziedziczą operację dodawania, co pozwala pisać No_Of_Apples := No_Of_Apples + 1; No_Of_Oranges := No_Of_Oranges + 1; Nie wolno oczywiście pisać: No_Of_Apples := No_Of_Oranges;
Typy pochodne 10 ale zamiast tego trzeba użyć konwersji No_Of_Apples := Apples'(No_Of_Oranges); Przypuśćmy, że dwie procedury obsługują sprzedaż obydwu rodzajów owoców procedure Sell (N : Apples); procedure Sell (N : Oranges); Możemy wywołać jedną z nich Sell (N => No_Of_Oranges); natomiast wywołanie Sell (6); jest niejednoznaczne i trzeba pisać Sell (Oranges'(6));
Typy pochodne 11 Jeżeli podprogram jest dziedziczony, to w rzeczywistości nie jest tworzony nowy podprogram. Wywołanie podprogramu dziedziczonego jest wywołaniem podprogramu macierzystego, przy czym parametry rodzajów in i in out są niejawnie konwertowane na typ macierzysty tuż przed wywołaniem, a parametry rodzajów in out i out są konwertowane niejawnie zaraz po wywołaniu podprogramu. Pisząc My_Apples + Your_Apples mamy Apples(Integer(My_Apples) + Integer(Your_Apples))
Typy pochodne 12 Zajmijmy się teraz ograniczeniami. Możemy tworzyć typy pochodne ograniczone. Przykład type Probability is new Float range ; Jest to równoważne dwóm deklaracjom: type Anonim is new Float; subtype Probability is Anonim range ;
Typy pochodne 13 Oznacza to, że podtyp Probability jest podtypem ograniczonym anonimowego typu pochodnego. Zauważmy, że zbiór wartości typu pochodnego jest kopią zbioru wartości typu macierzystego Float. Operacje " + ", " > " i inne, działają w całym nieograniczonym zbiorze wartości. Przykład Niech P : Probability; Można napisać P > 2.0 Mimo, że nie można podstawić wartości 2.0 do zmiennej P. Podane wyrażenie jest zawsze nieprawdziwe, chyba że zmienna P nie jest zainicjowana odpowiednio i przez przypadek ma złą wartość.
Typy pochodne 14 Rozpatrzmy teraz ograniczenia występujące w przypadku dziedziczonych podprogramów. Podprogram odziedziczony jest podprogramem macierzystym w którym wszystkie egzemplarze (instances) typu macierzystego są wymienione na typ pochodny. Podtypy są wymienione na równoważne podtypy z odpowiednimi ograniczeniami, a domyślne wyrażenia inicjujące są konwertowane przez dodanie konwersji typów. Dowolny parametr, albo wynik innego typu pozostaje niezmieniony.
Typy pochodne 15 Przykład Niech type T is...; subtype S is T range L..R; function F (X : T; Y : T := E; Z : Q) return S; przy czym E jest wyrażeniem inicjującym typu T, natomiast typ Q nie należy do klasy T, a więc jest typem całkowicie niezwiązanym. Jeżeli napiszemy type TT is new T; to z tego wynika, że napisaliśmy też subtype SS is TT range TT(L)..TT(P);
Typy pochodne 16 a nagłówek dziedziczonej funkcji F ma postać function F (X : TT; Y : TT := TT(E); Z : Q) return SS; W nagłówku typ T został zastąpiony przez TT, podtyp S przez SS, dokonana została konwersja wyrażenia E na wartość typu TT, natomiast typ Q został niezmieniony. Warto zauważyć, że identyfikatory parametrów formalnych zostały takie same. Typy pochodne są pewną alternatywą do typów prywatnych. Typy pochodne mają zaletę dziedziczenia literałów, ale często mają wadę dziedziczenia zbyt wielu rzeczy po typie macierzystym.
Typy pochodne 17 Przykład Weźmy pod uwagę deklaracje type Length is new Float; type Area is new Float; Wprowadzenie tych typów zabezpiecza przed mieszaniem długości i powierzchni, ale dziedziczona jest możliwość mnożenia dwóch egzemplarzy typu Length, w wyniku czego dostaniemy wartość typu Length. Poza tym, dziedziczymy wiele nieistotnych operacji jak np. potęgowanie.
Typy pochodne 18 Takie niepożądane operacje można, jeżeli trzeba, zastąpić podprogramami abstrakcyjnymi, takimi jak: function "*" (Left, Right : Length) return Length is abstract; Podprogram abstrakcyjny nie ma treści i nie można go wywołać. Każda próba wywołania wykrywana jest jako błąd podczas kompilacji.
Typy pochodne 19 Zalecenie Jeżeli jest wiele niepożądanych operacji dziedziczonych po typie macierzystym, często lepiej użyć typu prywatnego i zdefiniować te operacje których potrzebujemy. Zadanie Zadeklarować pakiet Metrics zawierający deklaracje typów pochodnych Length i Area wraz z odpowiednimi nagłówkami różnych operacji "*", "/", "**".