1 / 23

Logtalk

Logtalk. Objektum orientált kiterjesztés Prologhoz Bánsághi Zoltán bz341@hszk.bme.hu. Logtalk. Történet Smalltalk + logical = Logtalk Szerző: Paulo Moura 1994-95-ben Bonyolult (logikai) programok OO támogatásához készült 1998-99: 2.x, aktuális verzió: 2.15.4

dawson
Download Presentation

Logtalk

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. Logtalk Objektum orientált kiterjesztés Prologhoz Bánsághi Zoltán bz341@hszk.bme.hu

  2. Logtalk Történet • Smalltalk + logical = Logtalk • Szerző: Paulo Moura 1994-95-ben • Bonyolult (logikai) programok OO támogatásához készült • 1998-99: 2.x, aktuális verzió: 2.15.4 • Nyílt rendszer: www.logtalk.org • Teljes egészében Prologban íródott: előfordító és runtime predikátumok segítségével működik Mit próbál a Logtalk? • Objektumok kezelése (egységbe zárás, öröklődés) • Prototípus alapú és osztály alapú objektumhierarchiák együttes támogatása • Eseményvezérelt programozás • Automatikus dokumentáció generálás • ISO Prolog kompatibilis • A modul szervezésnek (ISO) alternatívája az OO szemlélet

  3. Objektumok :- object(list). :- public(length/2).% láthatóság/mód deklarációk :- mode(length(?list, ?integer), zero_or_more). % megoldások száma length(List, Length) :- length(List, 0, Length). % belülről elérhető length/3 length([], Length, Length). length([_| Tail], Acc, Length) :- % length/3 kívülről nem látszik! Acc2 is Acc + 1, length(Tail, Acc2, Length). :- end_object. • Objektumok: predikátumok összessége, ezek felelnek meg a metódusoknak, a tényállítások az attribútumok • Az objektumaink segítségével névterekre partícionálhatjuk a programunkat • Objektum azonosító: atom ill. struktúra (generikus objektumokhoz) • Láthatóság deklaráció: C++ szerű, így valósul meg az egységbe zárás • A láthatóságot kötelező deklarálni, különben csak belülről érhető el a predikátum • Mód deklaráció: ISO szerint, de CSAK dokumentálás céljára (egyelőre)

  4. Objektumok alkalmazása(azaz objektumok predikátumainak hívása) • Logtalk rendszer betöltése: | ?- consult('gnu.config'), %gazda rendszer függő predikátum deklarációk consult('logtalk.pl'). %független rész (fordító, runtime) yes • Objektum fordítása, betöltése (.lgt kiterjesztésű forrásból): | ?- logtalk_load([list]). yes • Üzenetküldés: | ?- list::length([1, 2, 3], L). L = 3 yes • Prototípus szerű objektumok: nem kellett külön példányosítani, a definícióval létre is jött valamilyen példány • Obj::Pred alakú infix operátor küld üzenetet, ez felel meg a metódus meghívásának • Nincs delegáció külön (mint a SICStus Objects-ben), üzeneteket delegálni csak az öröklési fában lehet • Pontosabban: ha egy szülő objektum metódusát implicit módon egy gyermeké(jé)n keresztül érjük el, akkor automatikusan delegálódik a gyermektől az üzenet (a szülőhöz) • :: Pred prefix operátor üzenetet küld az aktuálisan futó objektumnak, ez azért lehet fontos, mert nem feltétlenül egyezik meg avval amelyik kódjában szerepel ez a hívás

  5. Broadcast üzenetküldés • Lehetőség van egy üzenetet több objektumnak, illetve egy objektumnak több üzenetet is elküldeni • Ehhez a címzett illetve az üzenet helyére a megfelelő konjunktív vagy diszjunktív kifejezést kell írni: Obj::(Pred1, Pred2, ...)ekvivalense: Obj::Pred1, Obj::Pred2, ... Obj::(Pred1; Pred2; ...)ekvivalense: Obj::Pred1; Obj::Pred2; ... (Obj1, Obj2, ...)::Pred)ekvivalense: Obj1::Pred, Obj2::Pred, ... (Obj1; Obj2; ...)::Pred)ekvivalense: Obj1::Pred; Obj2::Pred; ...

  6. Futási kontextus, üzenetküldés • A rendszer követi az aktuális futási környezetet (kontextust), ami egy objektum • Ez a kontextus azt mondja meg, hogy melyik objektum viszonylatában kell értelmezni a predikátum definíciókat • Nézzük hogy egy Obj::Pred alakú üzenetküldés során mi történik: • Láthatóság ellenőrzése: az aktuális futási kontextusból hívható-e az adott metódus? • Futási környezet megváltoztatása a címzett objektumra • Az üzenet predikátum nevének feloldása, azaz a kód megkeresése az öröklési fában kiindulva az aktuális kontextusból (kötött sorrendben, így fedik el egymást az azonos nevű metódusok) • Az üzenet kódjának végrehajtása (tehát ha nem az aktuális kontextusban találtuk meg a kódot, akkor sem változik meg a környezet, azaz delegálunk) • Visszatérés sikerrel vagy meghiúsulással, a kontextus visszaállítódik az adott üzenetküldés előttire • Ha a ::Pred operátort alkalmazzuk akkor az épp olyan mintha explicit az aktuális környezetnek küldtünk volna üzenetet • self(S) visszaadja a kontextust • Ha egy predikátum hívás elé nem rakunk operátort, akkor az adott forrásban található hívódik meg (a források objektumoknak, az objektumok pedig névtereknek felelnek meg)

  7. square.lgt: :- object(square). :- public(side/1). :- public(area/1). :- public(side_def_diff/1). side(1). % default oldalhossz az örökléshez side_def_diff(A) :- % az aktuális oldal eltérése a defaulttól ::side(S1), side(S2), A is S1-S2. area(A) :- ::side(S), A is S*S. :- end_object. mysquare.lgt: :- object(mysquare, extends(square)). % beillesztés az öröklési fába side(2). % elfedi az alapértelmezettet :- end_object. Futtatás: | ?- mysquare::side_def_diff(D). D = 1 yes Látható a különbség side és ::side hívások között: az előbbi a „bevasalt” értéket jelenti, míg az utóbbi a megfelelő leszármazott objektum predikátumát, hiszen ha kód örökítés útján került a végrehajtás side_def_diff-re, a kontextus akkor is a megfelelő gyerek marad Prototípusos öröklődés

  8. Öröklés extrák • Többszörös öröklődésre van lehetőség, de névütközés esetén nem garantál semmit rendszer (tehát nem definiált a szülők prioritása, mint SICStus-ban) • Az öröklési fában mélységi módon keresünk, és az első megfelelő funktor nyer • Nincs beépített lehetőség az öröklés szűkítésére, azaz arra, hogy a szülő bizonyos metódusait ne örökölje a gyermek (de kézzel definiálhatunk meghiúsuló, vagy hibát dobó metódusokat, eltakarva az örökölt metódusokat) • Viszont van lehetőség láthatóság-korlátozott öröklésre, azaz az öröklött metódusok láthatóságát korlátozzuk :- object(Object, % ami Parent-ben public az itt protected lesz extends(protected::Parent)). ... :- end_object.

  9. Teljesen rejtett predikátumok :- object(list). :- public(length/2). %:- private(length/3). length(List, Length) :- ::length(List, 0, Length). % így NEM érhető el length/3!! length([], Length, Length). length([_| Tail], Acc, Length) :- % length/3 kívülről nem látszik! Acc2 is Acc + 1, length(Tail, Acc2, Length). :- end_object. • Ha a fenti módon próbáljuk meg hívni length/3-at (azaz a Logtalk örökléses hivatkozásfeloldó rendszerén keresztül), akkor nem fog sikerülni, mert nem deklaráltuk, így nem regisztrálódott a rendszerbe a predikátum • Az ilyen predikátumokat csak közvetlenül a forrásból használhatjuk, mert láthatóságuk híján nem vesznek részt a névfeloldásban az üzenetküldéskor • Bármelyik piros jelet elhagyva működő kódot kapunk • Ha viszont alkalmaznánk a private deklarációt, de a hívást explicit self-nek szóló hívásként írnánk, akkor se lenne látható length/3 (biztos hatékonysági okokból)

  10. Protokollok • A predikátum direktívákat (láthatóság, mód, stb…) külön forrásfájlba helyezhetjük, amiket az objektumok forrásai beillesztenek (importálnak), ezeket hívjuk protokolloknak • Protokollok is örökölhetnek egymástól (prototípusos szerű öröklés) • Egy objektum több protokollt is használhat • Hatékonyabb újrafelhasználás • Interfész-implementáció, definíció-deklaráció szétválasztás • Ugyan abban a névtérben helyezkednek el mint az objektumok, és külön fordítási egységet képeznek • Ebből következik, hogy az objektumok fordítását is befolyásoló predikátum direktívákat mindig ki kell írni, annak ellenére, hogy protokollokat használunk (ilyenek a dinamizmusra és metapredikátumokra vonatkozó direktívák) • Protokoll beillesztésnél is lehet korlátozni a láthatóságot

  11. Protokollok példa :- protocol(listp). % protokoll definíció :- public(append/3). :- mode(append(?list, ?list, ?list), zero_or_more). :- public(length/2). :- mode(length(?list, ?integer), zero_or_more). :- public(member/2). :- mode(member(?term, ?list), zero_or_more). :- end_protocol. :- object(list, implements(listp)). % protokoll beillesztése ... :- end_object. :- object(difflist, % valamilyen másik lista implements(listp). ... :- end_object.

  12. Kategóriák • Kód újrafelhasználás újabb eszköze: predikátumok egy halmazát nem kell feltétlenül egy objektumokba foglalni, létrehozhatunk kategóriát is • Egy kategóriát bármely objektum importálhat, és akkor elérheti a kategória predikátumait, mintha benne lennének definiálva • Fontos részüket képezik a predikátum direktívák, mert csak így lesz elérhető kívülről is a kód, de természetesen használhatnak protokollokat • Smalltalk-ban és Objective-C-ben van hasonló (állítólag) • Kategóriák használatával nagyobb szabadságot élvezhetünk az öröklési hierarchia megtervezésekor, mert azonos kódon osztozó objektumoknak nem feltétlenül kell egyazon öröklési fában lenniük • Közös névtérben vannak az objektumokkal, és önállóan fordítódnak • Kategória beillesztésnél is lehet korlátozni a láthatóságot

  13. Kategóriák példa :- category(attributes, % kategória definíció implements(attributes_p)). % protokoll beillesztése :- dynamic(attribute_/2). % ezt ki kell írni set_attribute(Attribute, Value):- ::assertz(attribute_(Attribute, Value)). get_attribute(Attribute, Value):- ::attribute_(Attribute, Value). del_attributes(Attribute, Value):- ::retractall(attribute_(Attribute, Value)). :- end_category. :- object(person, imports(attributes)). % kategória importálása ... :- end_object. • Futtatás: | ?- person::set_attribute(name, paulo), person::set_attribute(gender, male). yes | ?- person::get_attribute(Attribute, Value). Attribute = name, Value = paulo ; Attribute = gender, Value = male ; no

  14. Generikus objektumok • Objektum azonosító lehet struktúra is, aminek behelyettesítetlen változóival paraméterezhetjük az objektumot minden alkalommal amikor üzenetet küldünk neki • Ennek segítségével készíthetünk általános algoritmusokat, vagy a paramétereket tekinthetjük valami féle attribútumoknak • A paraméterekre nem tudunk a nevük alapján hivatkozni, csak a sorrendjük alapján a következő beépített predikátumokkal: • parameter(+N,?Val) segítségével adott sorszámú paraméter értékét illesztjük, de N nem lehet változó (behelyettesített se!!) • this(?Struct) használatával Struct illesztődik az objektum azonosítójára (amiben benne vannak a paraméterek is) • Az objektumokat a funktor/aritás azonosítja, tehát azonos paraméterszámú, és azonos nevű, de különböző objektumokat nem definiálhatunk (öröklődéssel se) • Üzenetküldéskor a címzett illesztődik a megfelelő funktorú objektumra, és ha ez nem sikeres akkor hibát kapunk • Ilyen módon az üzenetküldés klózában behelyettesítetlen változók is értéket kaphatnak

  15. Parameter/2 (ellen)példa :- object(hanyadik(_,’bla’,_)). % paramétereket felesleges elnevezni :- public(param/2). % parameter-t nem lehet kívülről hívni %param(N,V) :- parameter(N,V). % megpróbáljuk „kivezetni”, de nem megy param(N,V) :- this(S), arg(N,S,V). % ez a megoldás %:- public(parameter/2). % deklarálni még lehet %parameter(N,V) :- param(N,V). % de a beépített eljárást elfedni már nem %:- public(param1/1). %egy(1). %param1(V) :- egy(N),parameter(N,V). % már ez se megy: nem fordul le :- end_object. • Futtatás: | ?- hanyadik(11,12,13)::param(2,A).% masodik argumentum „bla” kéne legyen uncaught exception: error(existence_error(object,hanyadik(11,12,13)), hanyadik(11,12,13)::param(2,_32),user) | ?- hanyadik(11,B,13)::param(3,A). A = 13 B = bla yes

  16. Generikus objektumok(öröklődéssel) :- object(osszeg(_,_)). % paramétereket fölösleges elnevezni :- public(ertek/1). %ertek(E) :- this(osszeg(A,B)), E = A+B.% nem jó, mert leköti a funktort ertek(E) :- parameter(1,A),parameter(2,B), E = A+B. % általánosan jó :- end_object. :- object(osszeg(A,B,_), % itt már el kell nevezni a paramétereket!! extends(osszeg(A,B))). % pontosan milyen paraméterű szülője van ertek(E) :- ^^ertek(E1), parameter(3,Q), E = E1*Q. :- public(nemnorm_ertek/1). nemnorm_ertek(E) :- ^^ertek(E). :- end_object. • ^^Pred operátor: mint a ::Pred operátor, de a névfeloldást a szülőknél kezdi, így lehetőség van felüldefiniáláskor az eredeti predikátumot is felhasználni • Ha nem sorszám alapján érjük el a paramétereket (hanem a név-struktúra alapján), akkor a leszármazottakra nem fog illeszkedni

  17. Generikus objektumok(másik példa) :- object(ellipszis(_X,_Y,_R1,_R2), % itt nincs szerepe a neveknek extends(...)). ... % ellipszis osztály metódusai :- end_object. :- object(kor(X,Y,R), extends( ellipszis(X,Y,R,R) ). % összekötjük a változókat % ide mást nem is kell írni, működni fog minden körre is! :- end_object. • Ez a módszer nem általánosítható: a szülő és a gyermek osztály paraméterei között nem valósíthatunk meg tetszőleges kapcsolatot • Ennek oka, hogy nem történik semmilyen predikátum hívás a szülőre való hivatkozáskor (akkor fordulunk a szülőhöz ha nincs definiálva egy metódus), hanem csak felépül(t) a név-struktúrája • Áthidalhatnánk ezt a problémát úgy, hogy a gyermek paramétereiből tetszőleges módon előállítjuk a szülőiét (logikailag összekapcsoljuk őket), és minden származtatandó metódus törzsében explicit módon továbbküldjük a hívást a (megfelelően felparaméterezett) szülőnek • Sajnos ilyenkor megváltozik a futtatási kontextus, ezért a gyermek által felüldefiniált predikátumokat elfedi a szülőé (explicit küldés helyett explicit delegációt kéne alkalmazni)

  18. Eseményvezérelt programozás • Esemény: minden üzenetküldés (csak Obj::Pred alakú) generál két eseményt: mikor elküldődik az üzenet (before esemény) és mikor visszakerül a végrehajtás a küldőhöz (after esemény); ez az üzenet típusa • Egy esemény tehát evvel a négyessel írható le: (Event, Object, Message, Sender) azaz (Típus, Címzett, Üzenet, Küldő) • Monitor: az az objektum amelyik vár adott eseményre, és ha az bekövetkezik, akkor meghívódik a megfelelő metódusa • Az esemény kezelését a before(?Object, ?Message, ?Sender) ill. az after(?Object, ?Message, ?Sender) predikátumok végzik, az esemény típusától függően • Bármely objektum lehet monitor, ha implementálja a fenti metódusok legalább egyikét • Eseménykezelő regisztrációja: define_events(@Event, @Object, @Message, @Sender, +Monitor) alakú hívás után minden eseménynél ami illeszkedik az első négy argumentumra, meghívódik a Monitor objektum eseménykezelője, és átadódik a középső három argumentum • A before eseménykezelő meghiúsulása az egész üzenetküldést meghiúsítja, after eseménykezelő meghiúsulása visszalépést eredményez az üzenet végrehajtásában (az üzenet következő megoldása jön) • Így kényszereket vezethetünk be, szűrhetjük a megoldásokat, vagy csak nyomonkövethetjük a végrehajtást

  19. Egy eseményvezérelt példa( main.lgt ) :- object(main). n(1). n(2). n(3).% több megoldást szolgáltató predikátum :- public(run/1). run(X) :- this(This), % eseményvezérlők törlése, majd beállítása abolish_events(_Event,_Object,_Message,_Sender,mod_monitor(_)), % külön monitor a before és after eseményeknek define_events(before, _, _, This, mod_monitor(2)), define_events( after, _, _, This, mod_monitor(3)), % innentől jön a „program” n(N1),% választási pont az üzenetküldés előtt % monitorozandó üzenetküldés: This::eredmeny(N1,X).% csak ::/2 üzenetküldés generál eseményeket :- public(eredmeny/2).% ha nem public lenne, nem hívhatnánk ::/2-vel eredmeny(Be, Ki) :-% maga az üzenet n(N2),% választási pont az üzenetben Ki is Be + N2 * 10. :- end_object.

  20. Egy eseményvezérelt példa( mod_monitor1.lgt ) :- object(mod_monitor(_), % a paraméter határozza meg mit „enged át” implements(event_handlersp)). % before/after deklarációi % illesztünk az üzenetre, hogy a belsejébe lássunk % az üzenet nevét nem kéne bevasalni, csak az argumentumai sorszámát before(_, eredmeny(Be,_), Sender) :- parameter(1,M), 0 is Be mod M,!, writeq(Sender), write(' jot kuld: '), writeq(Be),nl. % ide csak akkor jutunk, meghiúsulás kell: before(_, eredmeny(Be,_), Sender) :- writeq(Sender),write(' rosszat kuld: '),writeq(Be),nl, fail. % az üzenet elküldésekor még szabad változó behelyettesített értékét % is el tudjuk kapni! after( _, eredmeny(_,Ki), _) :- parameter(1,M), 0 is Ki mod M,!. after( Object, eredmeny(_,Ki), _) :- writeq(Object),write(' rosszat ad vissza: '),writeq(Ki),nl, fail. :- end_object.

  21. Egy eseményvezérelt példa( futtatás ) | ?- main::run(X). main rosszat kuld: 1% nem jut el a végrehajtás eredmény/2-be main jot kuld: 2% elkezd maga az üzenet predikátum is végrehajtódni main rosszat ad vissza: 12% belső választási pontok X = 22 ?% egyetlen megoldás ami átmegy a monitor szűrésén main rosszat ad vissza: 32 main rosszat kuld: 3 no • Küldő és fogadó objektum ugyan az, ezért az nem különül el • Monitorokat egyszerű létrehozni, ki-be kapcsolni, változtatni akár dinamikusan is • A logikai paradigmának köszönhetően kifinomult vizsgálódási feltételeket könnyedén fogalmazhatunk meg • Csak a public üzeneteket látjuk, mert csak az explicit küldés generál eseményt

  22. Dinamizmus • Lehetőség van arra, hogy bármilyen Logtalk elemet dinamikusan, futási időben hozzunk létre • A szokásos predikátum-adatbázis kezelő eljárások (assert, retract) használhatóak az objektumok metódusainak manipulálására ha azokat dinamikusnak deklaráltuk • Létrehozhatunk futási időben objektumokat (protokollokat, kategóriákat is teljesen analóg módon) a create_object(Identifier, Relations, Directives, Clauses) beépített eljárás segítségével: create_object(foo, [extends(bar)], [public(foo/1)], [foo(1), foo(2)]). ekvivalens avval mintha a következő kódot töltöttük volna be: :- object(foo, extends(bar)). :- dynamic. %nyilván dinamikus lesz :- public(foo/1). foo(1). foo(2). :- end_object. • Fordítási időben létrehozott objektumot akkor van értelme dinamikusnak deklarálni, ha ki akarjuk törölni a futás során ( abolish_object(Object) )

  23. Dinamikus lekérdezések • Futási időben le is kérdezhetjük a létezők különböző tulajdonságait • Ezek közül talán a legfontosabb az objektumhieararchiára vonatkozó kérdés: extends_object(?Object, ?Parent [, ?Scope]) alakú hívással például lekérdezhetjük egy objektum szüleit • Hasonlóan kategóriák, protokollok relációit is kérdezhetjük, de vigyázni kell, mert például a implements_protocol(?Object, ?Protocol [, ?Scope]) predikátum teljesülhet kategóriára és objektumra (az első argumentum helyén), hisz ezek mind implementálhatnak protokollokat • Az ilyen esetekben használhatjuk például a current_object(?Object) predikátumot, ami akkor teljesül, ha létezik az argumentum objektum • Egy objektum dinamizmusát az object_property(?Object, ?Property) eljárás segítségével tudhatjuk meg, a megoldás lehet: static, dynamic, vagy built-in • Egy objektum predikátumait mint megoldásokat felsorolja a következő hívás: current_predicate(?Functor/Arity) , de a láthatóságok figyelembe vételével (ha kívülről hívjuk, csak a public lesz látható) • Egy predikátum tulajdonságait így nyerhetjük ki: predicate_property(+Predicate, ?Property) ,ekkor információt kapunk a láthatóságról (public, protected, private), a dinamizmusról (mint az objektumnál), valamint arról, hogy hol definiáltatott illetve deklaráltatott a predikátum

More Related