A Prolog programozási nyelv

Alprogramok, modulok

A SICStus Prolog minden eljárást valamilyen modulban helyez el. Amig nem intézkedünk másképp, az eljárások alaphelyzetben a user modulba kerülnek. Ha Prolog programunkat strukturálni kívánjuk, akkor ún. modul-állományokat kell létrehoznunk. Egy állományban egy modult tudunk elhelyezni, az állomány első programeleme egy modul-parancs kell legyen:

:- module(Modulnév,[Funktor1,Funktor2,...]).

Itt Funktor1, ... a modulból exportálni kivánt eljárások funktorai (azaz Név/Aritás alakú kifejezések, ahol Név egy atom, Aritás egy egész). Például, ha egy korábban definiált fennsik/3 eljárást egy modulba kivánjuk foglalni, akkor a szükséges eljárásokat be kell írnunk egy állományba, mondjuk plato.pl -be, és ennek az állománynak az elejére el kell helyeznünk a következő parancsot:

:- module(plato_kereses,[fennsik/3]).

Ha ezután be akarjuk tölteni ezt a modult, akkor a SICStus rendszer promptjánál ki kell adnunk egy use_module parancsot, argumentumában az állománynévvel:

:- use_module(plato).

Ez a parancs az adott állományban levő modult betölti, és az általa exportált összes eljárást importálja a kurrens modulba (példánkban a user modulba). Ezáltal az importált eljárások ebből a modulból hivhatókká válnak. Ugyanezt a beépitett eljárást használhatjuk a SICStus könyvtárak betöltésére is, pl. a lists könyvtárat a következőképpen tölthetjük be:

:- use_module(library(lists)).

A use_module beépitett eljárásnak van egy kétargumentumú változata, ez betölti a modult, de csak azokat az eljárásokat importálja, amelyek funktorai szerepelnek a második argumentumbeli import-listában. Pl:

:- use_module(library(lists),[last/2]).

csak a last/2 eljárást fogja láthatóvá tenni, a többi könyvtári eljárást nem. Ekkor például lehet egy saját append eljárásunk, anélkül, hogy ez a könyvtári példánnyal összeütközésbe kerülne. A use_module parancs szerepelhet egy állományban, akár egy modul-állományban is. Ez utóbbi esetben csak ebbe a modulba fogja importálni a betöltött eljárásokat. Ugyanazt a modul-állományt több modulba is betölthetjük, ez nem jár felesleges memóriafoglalással, mivel a SICStus Prolog rendszer az eljárásokat csak egy példányban tárolja.

A SICStus Prolog rendszer modulfogalma nem szigorú. Bármely betöltött eljárás meghívható, ha az ún. modul-kvalifikált hívási formát használjuk, azaz az eljáráshívás elé írjuk a modulnevet, attól a kettőspont operátorral elválasztva. Ha például a fennsík kereső programot a fent példaként idézett módon foglaltuk modulba, és azt betöltjük, akkor a fennsik/3 eljárást modul kvalifikálás nélkül tudjuk hívni, de a többi eljárást is meghívhatjuk, például:

?- plató_keresés:első_fennsik([1,2,2,3],4,F,H,L).

A SICStus rendszernek ez a tulajdonsága különösen hasznos modularizált programok nyomkövetésénél.

Végezetül következzék néhány, a magasabbrendű eljárások használata során felmerülő, a modularitással kapcsolatos fontos tudnivaló. A magasabbrendű (meta-) eljárás egy olyan eljárás, amelynek egy másik eljárás az argumentuma. Világos, hogy ha modulközi meta-eljárásokat írunk, azaz a meta-eljárás által meghívandó eljárás más modulban van, akkor szükség van arra, hogy az eljárás meta-argumentumát modul-kvalifikált módon adjuk át. Ezért a meta-eljárásokra egy meta_predicate deklarációt kell megadnunk, amelyben jelezzük, hogy melyek az eljárás-argumentumok. A meta-deklaráció formája:

:- meta_predicate eljárásnév(argleiró1,...)

Itt az argleiró lehet a : jel, annak jelzésére, hogy az adott argumentum egy eljárás, vagy bármilyen más atom a többi argumentum jelzésére. Ez utóbbi helyeken szokás a be- ill. kimenő jellegre utaló jeleket elhelyezni (+,-,?).

Alapvetően kétféle modulfogalom lehetséges:

Név-alapú modell

Egy név minden előfordulása (eljárás, konstans, struktúra) vagy látható (visible) vagy lokális (local) az adott modulban. A lokális nevek csak az adott modulban láthatók, azaz más modulban nem nevezhetők meg. Ez legegyszerűbben úgy képzelhető el, hogy egy r lokális névnek egy m modulban való minden előfordulása átneveződik pl. az 'm:r' névvé.

Előnyök:

module m1. export(p/1). ... p(X) :- ..., X, ... ... module m2. import(p/1). ... q :- p(r). r :- ... module m2. import(p/1). ... q :- p('m2:r'). 'm2:r' :- ...

Hátrányok:

Eljárás-alapú modell

A kívülről való láthatóságot eljárásokhoz és nem nevekhez kötjük. Az adatnevek általában mindig láthatóak.

Előnyök:

Hátrányok:

Modul-környezet nyilvántartás

Futás közben a rendszer állandóan nyilvántartja, mely modulban vagyunk. Ez közönséges eljárások esetén az eljárás definícióját tartalmazó modul. A meta-eljárásokat (pl. p/1 alább) azonban átlátszóaknak kell deklarálni.

:- module_transparent p/1.

Az ilyen eljárások esetén a modul-környezet a hívótól öröklődik. A modul-környezetet használjuk annak megállapitására, hogy egy meta-hívást mely modulban értelmezzünk. Például:

:- module (m1, p/1). % az m1 modul exportálja a p/1 eljárást :- module_transparent p/1 p(X) :- ..., X, ... ... :- module m2. q :- p(r), ... r :- ...

A p eljárás meghívásakor m2 marad a modul-környezet, mert p/1 átlátszó, így X hívásakor az X=r eljárást m2-ben keressük.

Meta-argumentumok nyilvántartása

A meta-eljárásoknál meg kell nevezni a meta-argumentumpoziciókat, pl. egy olyan p/3 esetén, melynek a 3. argumentuma eljárás:

:- meta_predicate p(+,+,:).

A meta_predicate deklarációt minden olyan modulban szerepeltetni kell, ahol az adott eljárást meghívjuk! A fordítóprogram az adott argumentumpoziciót minden hívásban kiegészíti, pl:

:- module(m2). :- meta_predicate p(:). q :- p(r). --> q :- p(m2:r).