210 likes | 389 Views
Automatyczne dereferencje w języku SBQL Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik Komputerowych subieta@pjwstk.edu.pl Seminarium badawcze Katedry Inżynierii Oprogramowania PJWSTK 20 października 2011 r. Skąd wziął się problem?.
E N D
Automatyczne dereferencje w języku SBQL Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik Komputerowych subieta@pjwstk.edu.pl Seminarium badawcze Katedry Inżynierii Oprogramowania PJWSTK 20 października 2011 r.
Skąd wziął się problem? • SBQL postawił znak równości pomiędzy zapytaniami i wyrażeniami języka programowania. • Jest pierwszym językiem w historii informatyki, w którym zapytania są budulcem dla konstrukcji imperatywnych i abstrakcji języka programowania. • Jego semantyka musi więc uwzględniać fakt, ze niektóre (pod) zapytania będą zwracały referencje, a nie wartości. • Np. dla operatora podstawienia zapytanie z lewej strony operatora musi zwrócić referencję. • Inny języki zapytań nie wspominają o referencjach. • Semantyka i implementacja języka SBQL jest fundamentalnie oparta na przetwarzaniu referencji.
Niedobre podejście do problemu • Programista wstawia funkcję deref wszędzie tam, gdzie chce zmienić referencję na wartość z nią skojarzoną: • Zamiast pisać x + 5powinien pisać deref(x) +5 • Zamiast pisać Empwheresal > 1000 pisze Empwherederef(sal)> 1000 • Taki styl byłby dla programistów bardzo irytujący. • Funkcja deref została źle odebrana przez studentów zapoznających się z językiem SBQL. • Stąd należy jej się pozbyć z kodu programu poprzez automatyczne jej wstawianie do drzew syntaktycznych zapytań podczas kontroli typologicznej. • Najlepiej pozbyć się jej wszędzie, gdzie to jest możliwe.
Jak pozbyć się pisania deref? Start Start Non - algebraic where Non - algebraic where Left subquery Right subquery Left subquery Right subquery > Name Emp Algebraic2 Name Emp Algebraic2 Left subquery Right subquery Left subquery Right subquery Name sal integer 1000 Algebraic1 deref integer 1000 Name sal • Podczas kontroli typologicznej, na podstawie sąsiedztwa z operatorem, następuje wniosek, że deref jest potrzebna: • Następuje więc jej automatyczne wstawienie do AST. • Np. dla zapytania Empwheresal >1000 >
Kiedy należy pozbyć się pisania deref? (1) • Jeżeli sąsiadujący operator bezpośrednio wskazuje, że działa na wartościach, a nie na referencjach. • Np. x + yposiada operator arytmetyczny + wskazujący, że argumentami powinny być wartości, zatem zmieniamy wyrażenie na deref(x) + deref(y). • Wszystkie operatory arytmetyczne, stringowe, itd. • Cecha zaimplementowana w systemie ODRA. • Dla wyrażeń warunkowych: if exists(address) thenaddress.cityelse “No address” Powinno być zamienione na: if exists(address) thenderef(address.city)else “No address” • Nie jest to zaimplementowane.
Kiedy należy pozbyć się pisania deref? (2) • Jeżeli bag lub sekwencja (lub inna kolekcja) miesza referencje i wartości, to wszystkie wyrażenia zwracające referencje powinny być objęte funkcją deref: • Np. bag(x, y, 5) powinien być zmieniony na bag(deref(x), deref(y), 5). • Argument funkcji sum, avg, min, max zwracający referencję powinien być objęty funkcją deref. • Jeżeli argumentem operacji na bagach lub sekwencjach jest kolekcja referencji, zaś drugim argumentem jest kolekcja wartości, to do zapytań zwracających referencje powinien by zastosowany deref.
Poszukiwanie generalnego przypadku • Jest więcej takich przypadków. • Problem jest rekurencyjny, wymagający generalnego podejścia. • Sygnatury typów s1 i s2 mogą być typologicznie kompatybilne, ale różnić się wyłączcie tym, że w pewnym miejscu s1 zawiera wartość, zaś s2 zawiera referencję. • To może zdarzyć się przy dowolnej złożoności s1 i s2, na dowolnym poziomie hierarchii zanurzenia sygnatur w sygnatury • To zmusza do stosowania w ODRA funkcji derefeksplicite • Problem wygląda na dość trudny w ogólnym przypadku. • Szczęśliwie, znalazłem proste rozwiązanie • … no może nie takie proste….
Przypomnienie: jak działa kontrola typów w SBQL • Mechanizm symuluje rzeczywiste obliczenia podczas czasu kompilacji. • Zamiast rzeczywistej bazy obiektów i stosów używa metabazy oraz statycznych stosów S_ENVS i S_QRES • Symulacji dokonuje procedura static_eval, która działa prawie tak samo jak eval, ale nie przetwarza rzeczywistych obiektów • Elementami przetwarzanymi na tych stosach są sygnatury, • czyli typologiczne charakterystyki rezultatów zapytań. • Sygnaturą może być typ atomowy (integer, string,…), identyfikator węzła metabazy, statyczny binder n(s), gdzie s jest sygnaturą, itd. • Analogicznie do funkcji nested działa funkcja static_nested.
Suplementy (atrybuty) sygnatur • Suplementy sygnatur przekazują dodatkową informację związaną z daną sygnaturą, taką jak: • Mutowalność (zdolność do aktualizacji) • Liczność (cardinality) informacja o potencjalnej liczność danej kolekcji, np. [0..*], [0..1], … • Rodzaj kolekcji: element, bag, sequence, … • Nazwa typu (dla nazwowej zgodności typów) • …. parę innych da się jeszcze wymyśleć • Każdy operator języka zapytań implikuje prostą arytmetykę suplementów sygnatur • np. integer[mutable = true] + integer[mutable = true] = integer[mutable = false]
Gdzie jest problem? • Jeżeli mamy operator języka SBQL, który żąda unifikacji sygnatur (np. +, <, union, if,…), to mechanizm kontroli typów ma do czynienia z wyrażeniem s1s2, gdzie s1,s2 są sygnaturami argumentów operatora . • Jeżeli s1,s2 różnią się tym, że w pewnym miejscu jeden z nich zawiera typ atomowy t, a drugi identyfikator węzła metabazy typu t, to sygnatury są zgodne, ale przed wyrażenie zwracające id. węzła metabazy należy wstawić węzeł deref. • Niestety, sygnatury nie zawierają informacji o tym, który fragment AST odpowiada za wygenerowanie tego identyfikatora. • Wobec tego w ogólnym przypadku nie wiadomo gdzie deref wstawić.
No to rozwiązanie narzuca się samo… • Trzeba do sygnatur dołożyć informację o węźle AST odpowiedzialnym za daną sygnaturę • Tę informację wprowadzamy w postaci dodatkowego suplementu: • Nazwałem go ASTnode. Wartością suplementu jest identyfikator węzła AST. • Przykładowo: iname[mutable=true, coll=bag, ASTnode=5] sygnatura suplement mutable suplement rodzaj kolekcji suplement węzeł AST
Proceduraunifytypes(s1, s2) • Procedura unifytypesporównuje sygnatury s1i s2i zwraca wynikową sygnaturę dokonując unifikacji sygnatur s1i s2 w pewną wynikową sygnaturę s. • Dokonuje przy tym korekty AST poprzez wstawienie deref do odpowiedniego miejsca. • Procedura sprawdza także typologiczną zgodność s1i s2 ; jeżeli tej zgodności nie ma sygnalizuje błąd typologiczny. • Następnie naprawia AST tak, aby kontrola typów mogła być kontynuowana. • Procedura może być zastosowana we wszystkich przypadkach wymagających dereferencji, a także w innych przypadkach, np. zamiany elementu na bag.
Szkic procedury unifytypes(1) • W pierwszym kroku procedura dokonuje serializacji sygnatur s1is2, tak aby mogły one być porównywane. • Serializacja jest deterministyczna, np. poprzez sprowadzenie sygnatur do polskiej notacji. • Sygnatury union compatiblezawsze są serializowane w ten sam sposób. • Każdy element struktury występujący w sygnaturach jest elementem serializacji, ale variant{v1, v2, …, vk} jest uważany za pojedynczy element serializacji. • Po serializacji procedura obiega obie sygnatury w ten sam sposób i kolejności: dla i = 1,2,3,… porównuje s1[i] oraz s2[i].
Szkic procedury unifytypes(2) • Błąd typologiczy jest sygnalizowany jeżeli s1[i] oraz s2[i] są typologicznie niezgodne. • Błąd występuje jeżeli np. statyczne bindery mają różne nazwy, lub nie zgadzają się typy atomowe, lub liczność struktur jest różna. • Następnie sygnatury są naprawiane i procedura jest kontynowana • Dla automatycznych dereferencji istotny jest przypadek, kiedy w pewnym miejscu s1[i] ma wartość (nazwijmy ją a) zaś s2[i] jest identyfikatorem węzła metabazy (nazwijmy go r), przy czym typ r jest zgodny z a. • W takim przypadku procedura korzysta z suplementu ASTnode przypisanego do s2[i] i wstawia przed ten węzeł funkcję deref.
Szkic procedury unifytypes(3) • Analogicznie dla przypadku s1[i] = r i s2[i] = a • Jeżeli s1[i] = r1, s2[i] = r2to porównywane są typy siedzące pod r1 i r2. Jeżeli są zgodne, to deref wstawia się zarówno przed ASTnode pobrane z s1[i] jak przed ASTnode pobrane z s2[i]. • Jeżeli typy są niezgodne, to sygnalizuje się błąd i naprawia AST. • Dalsze założenia dotyczą przypadku z wariantami. • Szczegóły w nowym rozdziale książki, która się piszeeeeeeee…
Skromny przykład (1) Zapytanie: Emp.nameunionDept.dnameunion “Doe” AST: SygnaturaEmp.name:iname[mutable=true, coll=bag, ASTnode = 5] Sygnatura Dept.dname:idname[mutable=true, coll=bag, ASTnode = 8] Sygnatura “Doe”: string[mutable=false, coll=null, ASTnode=9]
Skromny przykład (2) • Proceduraunifytypes is zastosowana dla pierwszego union unifytypes(iname[mutable=true, coll=bag, ASTnode=5], idname[mutable=true, coll=bag, ASTnode=8]) = variant{iname[mutable=true, coll=bag, ASTnode=5], idname[mutable=true, coll=bag, ASTnode=8]} • static_eval zastosowany dla tego union zwróci sygnaturę: variant{iname[mutable=true, coll=bag, ASTnode=5], idname[mutable=true, coll=bag, ASTnode=8]}[mutable=true, coll=bag, ASTnode=2]
Skromny przykład (3) • Drugi operator unionimplikuje następne zastosowanie proceduryunifytypes: unifytypes(variant{iname[mutable=true, coll=bag, ASTnode=5], idname[mutable=true, coll=bag, ASTnode=8]} [mutable=true, coll=bag, ASTnode=2], string[mutable=false, coll=null, ASTnode=9]) = string[mutable=false, coll=bag] • Procedura unifytypeswstawi węzły deref przed węzły ASTnode=5 i ASTnode=8. • Zastosowanie drugiego union zwróci sygnaturę string[mutable=false, coll=bag, ASTnode=1]
Inne role ASTnodei unifytypes • W ten sam sposób można dokonywać koercji pojedynczych elementów na bagi, sekwencji na bagi, itd. • Identycznie z koercją integer na real i odwrotnie. • Identycznie z rzutowanie obiektu na obiekt z klasy bardziej generalnej, np. Prac Osoba • Podobnie z rzutowaniem dynamicznej roli na jej nadrolę. • Sygnalizacja błędów typologicznych: unifytypes potrafi zrobić to precyzyjniej, z dokładnością do węzła AST. • Jeżeli w tym węźle będzie jeszcze informacja o koordynatach kodu źródłowego, który go wygenerował, to można łatwo przenieść sygnalizację na poziom kodu źródłowego, np. przez podświetlenie błędnego kodu.
Czy da się w ogóle usunąć deref z języka? • Naszkicowana metoda pozwala uniknąć pisania derefeksplicite w większości przypadków. • Nie da się jednak tego zrobić we wszystkich przypadkach bez obniżania mocy języka. • Np. przy porównaniu q1= q2intencją programisty może być porównanie referencji lub porównanie wartości siedzących pod tymi referencjami • Zatem potrzebny będzie kod deref( q1) = deref( q2) • Podobnie z usuwaniem duplikatów. • Można próbować to zastąpić jakimiś słowami kluczowymi, ale to wygląda na zamianę siekierki na kijek.
Na zakończenie… • Pozornie nieduży problem przerodził się w całkiem spory. • Nie będzie łatwo zaimplementować go w ogólnym przypadku. • Mogą być dalsze komplikacje związane z dereferencjami dla złożonych obiektów. • Może trochę za mało tego na pracę doktorską, ale jeżeli problem byłby dostatecznie uogólniony, zaś implementacja powiązana z optymalizacjami, to kto wie… Dziękuję za uwagę. Pytania, komentarze, narzekania?