A Prolog programozási nyelv

Szabványos könyvtárak

A Prolog könyvtárhierarchiája nagyon terebélyes, és gyakorlatilag minden lényeges funkció megvalósított, az I/O műveletektől az ablakozó-rendszerig. A szabványosság kérdése ugyanakkor nem megoldott, ahány implementáció annyiféle könyvtár-hierarchia.

Programbetöltés

Adott forrásfájl vagy tárgykód betöltését jelenti a Prolog fejlesztői környezetbe. Szabványos predikátuma az ensure_loaded/1, paramétere egy fájlnév, vagy fájlnevek listája. Minden fájlra nézve ellenőrzi annak korát, és csak akkor tölti be, ha szükséges.

Kiirás, beolvasás

A kifejezések írása és olvasásakor fontos szerepet játszanak az operátorok. Fontos, hogy ügyeljünk arra, hogy a kiíráskor élő operátorok beolvasáskor is éljenek, különben a beolvasás nem (vagy nem megfelelően) fog sikerülni.

Kifejezések kiírása

A write/1 és write_term/2 predikátumok segítségével tetszőleges Prolog termet írathatunk ki a képernyőre. A write_term/2 második argumentuma, egy Prolog lista, formázási opciókat tartalmazhat.

Opciók:

quoted(X)
Ha X=true, minden olyan nevet, ahol ez szükséges szimpla idézőjelek között ír ki, hogy a read/1 predikátum ezt be tudja olvasni.
ignore_ops(X)
Ha X igaz, a term a standard Prolog-forma szerint kerül a képernyőre. Minden operátoros kifejezés prefix formát ölt.
portrayed(X)
Ennek választásával a write meghívja a portray/1 predikátumot, így saját formát adhatunk termjeinknek.
numbervars(X)
X igaz értéke esetén a belső reprezentáció helyett (pl.: _215) '$VAR'(N) alakú termek (pl.: A vagy C5) kerülnek a képernyőre. Lásd még: numbervars/3.
cycles(X)
Ha X=true, minden esetlegesen ciklikus term véges alakban, a @/2 predikátum segítségével kerül kiírásra.
indented(X)
A megadásával a termek megfelelő behúzással kerülnek a képernyőre.
max_depth(X)
X-nek megfelelő mélységig írja ki az egymásba ágyazott termeket. X=0 esetén nincs limit.
character_escapes(X)
X igaz értéke esetén minden atomban a speciális karakterek helyett azok escape-szekvenciái fognak szerepelni.
float_format(X)
Megadja a lebegőpontos számok kiírásának formátumát. X itt '~NC' alakú kell hogy legyen (lásd: format/2). Az alapérték: '~H'.
priority(X)
A termet úgy írja ki, mintha az egy X prioritású asszociatív operátor hatókörében lenne. Az alapérték 1200.

A writeq/1 úgy működik, mint a write/1 azzal a különbséggel, hogy szükség esetén a nevek idézőjelek közé kerüljenek, ha a read-nek szüksége van erre, a helyes beolvasáshoz. A kiírt operátoroknak a beolvasáskor deklarálva kell lenniük. A write_canonical/1 is hasonló, ám az operátorok nem infix, hanem szabványos formában kerülnek a kimenetre (pl.: 1+2 +(1,2)).

A print/1 alapértelmezésben azonos write-tal. Ha a felhasználó definiál egy portray/1 eljárást, akkor a rendszer minden a print-tel kiírandó részkifejezésre meghívja azt. Ha ez a hívás sikerül, akkor feltételezi, hogy a portray elvégezte a szükséges kiírást. Ha meghiúsul, akkor maga írja ki a részkifejezést.

A portray/1 olyan, a felhasználó által definiált (kampó) eljárás (hook predicate), amely saját formát ad egyes termeknek. Ha az eljárás meghiúsul, a Prolognak magának kel kiírnia az argumentumban szereplő kifejezést. Például a portray(szemely(Nev,_,_)):-write(szemely(Nev,...)). predikátum a szemely(kiss,istvan,1960) termet szemely(kiss,...) formában fogja kiírni.

A format/2 predikátum az első paraméterben megadottak szerint kiírja a második paraméterként kapott termet. A formátum-paraméter alakja ~<szám><formázójel>, a fontosabb formázójelek pedig a következők:

Például

simple_statistics(Term) :- statistics(Term,RunT,Inf), format('~tStatistics~t~72|~n~n'), format('Runtime: ~'.t ~2f~34| Interferences: ~'.t ~D~72|~n',[RunT,Inf]).

Eredménye

Statistics Runtime: ............. 3.45 Interferences: ........ 60.345

Kifejezések beolvasása

A read/1 és a read_term/2 beolvas egy ponttal lezárt kifejezést, egyesíti azt az első argumentummal és kitölti a megadott opció-listát (read_term második argumentum). A read(X) megegyezik a read_term(X,[]) predikátum-hívással.

Opciók:

variables(X)
X a beolvasott kifejezésben szereplő változók listája (balról jobbra haladva)
variable_names(X)
X itt N=V alakú párok listája, ahol V a beolvasott kifejezésben szereplő nem névtelen változó, A pedig egy atom, amely nyomtatott alakja megegyezik a változó nevével.
singletons(X)
X itt is N=V alakú párok listája, ahol V egy a beolvasott kifejezésben pontosan egyszer előforduló nem névtelen változó, A pedig egy atom, amely nyomtatott alakja megegyezik a változó nevével.

Karakterkonverzió

A char_conversion(X,Y) predikátum hozzáveszi a az érvényben levő konverziós táblához az X,Y párt. Ezután minden kifejezés beolvasásakor X összes nem idézőjelek közti előfordulását lecseréli Y-ra. Ha a két argumentum megegyezik, akkor a korábbi Y karakterhez tartozó konverziós párt eltávolítja a leképezésből.

A current_char_conversion(X,Y) predikátum lekérdezi a konverziós táblát, és megnézi, szerepel-e benne az X,Y pár. Használható még az X-hez vagy Y-hoz tartozó konverziós pár lekérdezésére is.

File I/O műveletek:

A szövegfájlokat háromféle módban lehet megnyitni, ezek: read, write, append. Megnyitás az open/3 művelettel történik, argumentumai sorra: fájl neve, megnyitás módja és a csatorna neve.

Például, ha az F fájlból akarunk olvasni:

open(F, read, R)

Ezután műveleteink az R csatornára vonatkoznak, egészen a csatorna lezárásáig. Ezt a close/1 művelettel tehetjük meg. A szabványos bemeneti és kimeneti csatornákra user néven hivatkozhatunk, ezeket nem kell (és nem is lehet) megnyitni és lezárni.

Az I/O utasítások lehetnek karakter, vagy term szintűek.

Karakter szintű utasítások:

  • peek_code(F,C) ez akkor igaz, ha az F csatorna aktuális karakterének kódja egyezik C-vel.
  • get_code(F,C) hasonló, mint a peek_code(F,C), csak hatására az F csatorna aktuális karaktere a következő lesz.
  • at_end_of_stream(F) igaz, ha az F csatornát már végigolvastuk.
  • put_code(F,C) az F csatornára kiírja a C kódú karaktert
  • nl(F) sort emel

Term szintű utasítások:

  • read(F,T) egy termet olvas be az F csatornáról és megpróbálja T-vel illeszteni. A beolvasandó termet mindig egy pont és egy elválasztó karakter zárja le.
  • write(F,T) kiír egy termet az F csatornára, lezáró pont nélkül, tehát általában nem visszaolvasható kimenetet kapunk (hisz az feltételezi a pont és elválasztó karakter jelenlétét).
  • writeq(F,T) ezt akkor használjuk, ha visszaolvasható kimenetet szeretnénk, a T termet lezáró ponttal együtt írja ki.

Például ha egy fájl végéhez hozzá akarunk fűzni egy másik fájlt, az elsőt append módban, a másodikat read módban nyissuk meg. Érdemes karakterenként beolvasni, és kiírni, ha nem tudjuk, hogy a bemenet termenként olvasható-e. A „-1”-es karakterkód jelenti a fájl végét, ekkor zárjuk le a csatornákat.

class="source" % appf(F1,F2) :- az F1 fájl végére beszúrja F2.t appf(F1,F2) :- open(F1,append,A), open(F2,read,R), af(A,R) af(A,R) :- get_code(R,C), ( C == -1 -> close(A), close(R) ; put_code(A,C), af(A,R) ).

PLN5 - függvények a Prologban

Dr. Ásványi Tibor több munkájában is foglalkozik a Prolog PLN (ProLog with Narrowing) adta funkcionális bővítésével. Az egyszerű PLN-beli függvények <head> = <expression> [ :- <condition> ]. alakúak, és néhány operátor segítségével használatukat problémamentesen integrálni lehet a nyelvbe (lásd: Ásványi, Tibor: User's Functions in Standard Prolog). Az alábbi kis példa a Fibonacci-sor egy elemének kiszámításán keresztül szemlélteti a PLN-függvények használatát.

% PLN function fib/1 fib(N) = fib(N-1)+fib(N-2) :- integer(N), N>1. fib(1) = 1. fib(0) = 1.

Ezzel a definícióval és a ?X=fib(2) kifejezés használatával (? jelöli egy PLN-kefejezés kiértékelését) a Prolog először minden argumentumot kifejt, így X megmarad helyettesítetlen változónak, míg fib(2) előbb fib(2-1)+fib(2-2), majd fib(1)+fib(2-2), azután 1+fib(2-2), 1+fib(0), 1+1, végül 1+1, azaz 2 lesz, amit így illeszthetünk (X=2 maiatt) X-szel.

Az egyszerű függvényeken kívük esetszétválasztásos függvények is implementálhatók, a szokásos Prolog jelölések használatával, így ez az eszköz igen általános, hasznos bővítése az ISO Prolog nyelvnek.

fibonacci(N) = { \+ integer(N) -> throw(type_error) ; N >= 0 -> fib(N) ; throw(constraint_error) }. fib(N) = { N>1 -> fib(N-1)+fib(N-2) ; 1 }. fibon(N) = { integer(N), N >= 0 -> fib(N) }.

DCG - fordítóprogramok

A Prolog visszalépéses keresési rendszere alkalmas nyelvtani ellenőrzések, fordítóprogramok készítésére. Érdekes módon a rendszer önmagát használja saját inputja felismerésére, s ha már egyszer adott ez az eszköz, elérhetővé is teszi a felhasználó számára. A neve Definite Clause Grammar (DCG), ami magyarul talán a tisza klóznyelvtannak felel meg. Nézzük hát, hogyan is néz ki egy DCG-kifejezés. Jelrendszere a standard Prolog kifejezésekre épül. A fej után egy nyilat követve jön az aktuális blokk kifejtése, esetleges kapcsos zárójelek közti Prolog kifejezéssel. A num(N) --> [N], {integer(N), N>=0}. DCG-predikátum például egy nemnegatív egészet értelmez le az inputról és ezzel tér vissza.

Vegyük például az alábbi négy alapműveletes számolást leíró nyelvet, és írjunk rá egy programot, ami nemcsak értelmezi az inputot, de ki is számolja az eredményt.

<arith> ::= <expr> {+,-} <arith> |
            <expr>
<expr>  ::= <num> {*,/} <expr>   |
            <num>
<num>   ::= {a natural number}

A nyelv tehát nem bonyolult, a fordítóprogramunk pedig még kevésbé lesz az. Mit is kell csinálnunk az első esetben? A bemenetet, mint expr értelmezzk, és ha van még utána valami hát megnézzük, hátha + vagy - jel, ami után biztosan egy arith alakú kifejezés következik. Ha nem +, és nem is -, a visszalépéses keresés továbblép, és expr-ként tekint az input egészére. Az érdekes az ezután következő Prolog-blokk, ami a már fölismert inputra ad megkötéseket, illetve értelmezést (pl.: {integer(N), N>=0} vagy {X is X1 * X2}).

arith(A) --> expr(A1), [+], arith(A2), {A is A1 + A2}. arith(A) --> expr(A1), [-], arith(A2), {A is A1 - A2}. arith(A) --> expr(A). expr(X) --> num(X1), [*], xpr(X2), {X is X1 * X2}. expr(X) --> num(X1), [/], xpr(X2), {X is X1 / X2}. expr(X) --> num(X). num(N) --> [N], {integer(N), N>=0}.

A Prolog nyelvén egy-egy ilyen szabály mindig gyarapszik még két argumentumal, az input listával és az értelmezés után fennmaradtal. Tehát ha ellenőrizni szeretnénk, programunk működését az arith(X,[1,+,2,*,3],[]) célhoz hasonló kérdést kell a Prologhoz intéznünk. Nem maradt más hátra, mint hogy megnézzük, hogy is néz ki a DCG kissé kusza formális definíciója.

<DCG_formula>        ::= <DCG_clause> '-->' <DCG_clause>           |
                         <DCG_clause> '-->' <DCG_clause> ','
                                           '{'<Prolog_expression>'}'
<DCG_clause>         ::= '['<term>']'                              |
                         '['<term>']' ',' <DGC_clause>             |
                         <name>['('<var_list>')']                  |
                         <name>['('<var_list>')'] ',' <DGC_clause>
<var_list>           ::= <var>                                     |
                         <var>',' <var_list>
<var>                ::= {Prolog változó}
<name>               ::= {Prolog névkonstans}
<term>               ::= {Prolog term}
<Prolog_expression>  ::= {tetszőleges Prolog kifejezés}