Andmetüübid

Üks sageli kasutatav programmeerimiskeelte liigitus põhineb andmetüüpide kasutamise rangusel. Nn. "rangete tüüpidega" keeltes (strongly typed languages) nõutakse, et muutujate andmetüüp oleks muutuja deklaratsioonis määratud ja muutujatele väärtuste omistamisel kontrollib translaator, et omistatavate väärtuste tüüp vastaks muutuja deklaratsioonis määratud tüübile. See väldib palju programmeerimisvigu, kuid teeb teisalt programmeerijale lisatööd - näiteks reaalarvu (float, real) tüüpi muutujale väärtuse 3 omistamisel võib tulla veateade - 3 on täisarv (integer) ja see tuleb enne omistamist muuta reaalarvuks. Range tüübikontroll on tavaline kompileeritavates keeltes; interpreteeritavates keeltes on enamasti olulisem programeerimismugavus ja siin muutujate tüüp ei ole fikseeritud - samale muutujale võib omistada nii täisarvulise väärtuse kui ka näiteks tekstikonstandi (stringi), interpretaator püüab omistatud väärtust ise sobitada, s.t. teeb tüübiteisendusi, kui need vajalikud on.

Ka Prologis (enamuses realisatsioonidest) ei ole ranget tüübikontrolli ja muutujate tüüpi ei deklareerita. See on kooskõlas Prologi üldise filosoofiaga - Prolog on vahend maailma kirjeldamiseks ja tavaliselt me ei piiritle rangelt reaalsete väärtuste tüüpe (kas kuupäev on arv, tekst või mingi kolmandat tüüpi suurus?); väärtuste tüübid muutuvad olulisteks arvutis, kus need väärtused tuleb sobitada arvuti fikseeritud mälustruktuuriga.
Kuid Prologist ja tema töö peensustest paremaks arusaamiseks on siiski vajalik teada, kuidas Prolog väärtusi liigitab, s.t. millised on Prologi (sisemised) andmetüübid - kuigi Prolog ei nõua muutujatele tüüpide määramist, on sageli oluline, milliseid suurusi ja kuidas Prolog kasutab.

Prologi ja nn. käsukeelte (imperatiivsete keelte - C, C++, Pascal jne) erinevuse mõistmiseks on tarvis meeles pidada, et Prolog käsitleb kõike sümbolite jadadena - termidena; term on Prologi kõige üldisem andmetüüp.

Termi süntaks on sama, mida järgitakse programmis faktide kirjeldamisel, reegli parem ja vasaku poole osad (komadega eraldatud) on termid jne.

Kogu Prologi töö põhineb termide samastamisel (unification), s.t. termides esinevate muutujate asendamisel teiste termide või konstantidega nii et termid muutuvad võrdseteks (samastuvad). Samastamise õnnestumiseks on sageli tarvis enne analüüsida termide struktuuri - kas term on konstant (mis tüüpi - arv, tekst?) või muutuja, või on tegemist struktuurse termiga, millel on funktor (milline?) ja argumendid (kui palju?) jne. Termide (s.t. kogu Prologi programmi, andmete jne) kirjutamisel võib Swi-Prolog-is kasutada ka nn. täpitähti (ä,ö,õ jne), kuid nende kasutamisest faili- ja moodulinimedes peaks hoiduma.

Termide analüüsiks ja teisendamiseks on Prologis palju süsteemipredikaate.

Termid liigitatakse nende süntaksi (kirjaviisi) põhjal: atom, number, var (muutuja), string, list (nimistu), compound (struktuur). Kõigi nende jaoks on olemas spetsiaalpredikaadid, mis kontrollivad kas argument on seda tüüpi.

Kõige sagedamini esineb Prologi programmides aatom - väiketähega algav tekstikonstant (tühikuteta; aatomid on näiteks kõik predikaatide nimed); suvalise märgijada aatomiks tegemiseks (esineb tühik, algab arvuga jne) peab selle võtma ülakomade vahele; atomi tüüpi kontrollib predikaat atom(X), näiteks:

?- atom(a).
yes
?- atom(a43).
yes
?- atom('a s').
yes
?- atom('a S 34').
yes
?- atom(5a).
no

Aatomid on (väikesed) sümbolite jadad (sarnanevad veidi stringidega, vt. allpool) ja nende käsitlemiseks on palju sisseehitatud predikaate:
- atom_concat(Atom1, Atom2, Atom1Atom2) - ühendab (konkateneerib) aatomid Atom1, Atom2 aatomiks Atom1Atom2; kui kolmandal argumendil Atom1Atom2 väärtus (s.t. see on aatom) ja Atom1, Atom2 on muutujad , siis annab tagurdamise käigus muutujatele Atom1, Atom2 kõikvõimalikud väärtused, mis ühendamisel annavad antud aatomi Atom1Atom2;
- concat_atom(List,Atom) - ühendab aatomitest koosneva nimistu List elemendid üheks aatomiks Atom; atom_length(Atom, Pikkus) - leiab aatomi pikkuse;
- sub_atom(Atom, Index, Pikkus, SubAtom) - leiab aatomi Atom alamlõigu (alamaatomi), mis algab kohalt Index ja mille pikkus on Pikkus; näiteks päringu sub_atom(programmeerimine,4,5,S) vastuseks saadakse S = gramm jne.

Integer on täisarv vahemikus -2147483648..2147483647 (s.t. 32-bitine); tüüpi kontrollib predikaat integer(X) :

?- integer(2147483647).
Yes
7 ?- integer(2147483648).
No

Predikaat atomic(X) on tõene, kui argument X on atom või täisarv:

?- atomic(5).
yes
?- atomic(asd).
yes
?- atomic('a S').
yes
?- atomic(200000000000).
no

Reaalarvus reaalosa eraldaja on punkt (koma on nimistu liikmete eraldaja); reaalarv ei või alata ega lõppeda punktiga; reaalarvus võib kasutada ka järku ja selle tüüpi kontrollib predikaat float(X):

?- float(4.5).
yes
?- float(-2.873572).
yes
?- float(-0.1e-20).
yes
?- float(2.3E50).
yes
?- float(.3).
ERROR: Syntax error: Operator expected
ERROR: float(.

Predikaat number(X) on tõene, kui X väärtuseks on täis-või reaalarv:

?- number(3).
yes
?- number(-2.03e-200).
yes

Arvutamiseks on olemas kõik tavalised aritmeetikafunktsioonid: +,-,*,/ , ** (astendamine), max, min, mod, exp, abs, ln, log10, sqrt (ruutjuur) jne, trigonomeetrilised funktsioonid: sin, cos, tan, asin (arcsin), acos, atan jne.

Matemaatiliste valemite kirjutamisel peab meeles pidama, et Prolog käsitleb valemit nagu termi, s.t. näiteks = kasutamisel püüab samastada võrduse paremal ja vasakul pool olevaid terme:

?- X+3=5+Y.
X = 5
Y = 3
?- X = 2+3.
X = 2 + 3
yes
s.t. muutuja X samastati termiga 2+3. Järgnev ebaõnnestub, sest neid terme ei saa samastada (ühel pool "+", teisel pool "-"):
?- X+3 = 5 - Y.
no
Kui tahetakse arvutada matemaatilise avaldise väärtust, tuleb kasutada süsteemipredikaati is :
?- X is 2+3.
X = 5
yes
Predikaat is ei ole imperatiivsetes keeltes (C, Pascal) kasutatav omistamine, vaid samastamise erijuht, kus enne samastamist leitakse ka paremal pool oleva avaldise väärtus. Prologis ei saa kirjutada ega kasutada
X is X + 1.
- selle järel tuleb (vähemalt) veateade ja kogu kogu programm võib "õhku lennata", sest selline avaldis on loogiliselt võimatu: X ei saa olla võrdne X+1-ga.

Kahe muutujateta arimeetilise avaldise võrdlemiset kiirendab predikaat =:= :

?- 2+7 =:= 10-1.
Yes

X+3 =:= 10.
ERROR: Arguments are not sufficiently instantiated

String on sümbolite jada, mis on võetud kas dollarimärkide (erineb LPA-st!) või tagurpidi appostroofide ` ` vahele (tavalised jutumärgid ei ole stringieraldajad!); stringe kontrollib predikaat string(X):

?- string($Nagu me kõik teame$).
yes
?- string(`ka see on string`).
yes
?- string("appi").
no

Stringide töötlemiseks on palju sisseehitatud predikaate: string_atom(String, Atom) (teisendab stringi aatomiks ja vastupidi), strcat(StringA, StringB, StringAB) (ühendab stringid StringA, StringB stringiks StringAB), string_length(String, Pikkus) (leiab stringi String pikkuse), sub_string(String, Index, Length, SubString) (leiab stringi String alamstringi SubString, mis algab kohalt Index ja on pikkusega Length, analoogiline vastava aatomite käsitlemise predikaadiga), string_integer(String, Integer) (muudab täisarvu selle numbrite stringiks ja vastupidi, näit tõene on string_integer(`123`, 123) jne; kõigi süsteemipredikaatide kohta lisainfot vt. Help.

Kuigi stringid ja aatomid näivad üsna sarnaste tüüpidena, on nende käsitlemises oluline erinevus: aatomid kannab interpretaator nn. nimede tabelisse ja programmis kasutatakse vaid viita sellesse tabelisse (s.t. aatomit esindab programmis mingi täisarv), stringe aga käsitletakse "nagu nad on", s.t. märkide jadadena. See teeb aatomite käsitlemise oluliselt kiiremaks kui stringide käsitlemise.

Nimistute (list) süntaksit kontrollib süsteemipredikaat is_list(X), näiteks

is_list([2,a,d7]).
yes

Nimistute töötlemisel on peale [Pea|Keha] notatsiooni kõige tähtsamad
memberchk(Term, List) - kontrollib, kas Term esineb nimistus List (seda ei saa kasutada tagurpidi, s.t. nimistu List kõigi elementide genereerimiseks muutujas Term; ka selles suunas töötav predikaat on kirjeldatud eelmises peatükis), sort(Nimistu, Järjestatud) - sorteerib (järjestab) nimistu elemendid ja eemaldab korduvad elemendid, msort(Nimistu, Järjestatud) - sama kui eelmine, kuid korduvaid ei eemalda, length(Nimistu, Pikkus) jne. Väga kasulik on Swi-Prologi predsort(Pred,Nimistu, Järjestatud), kus Pred peab olema predikaadi nimi, mille abil on defineeritud suhted <, >, = ja see peab olema kirjeldatud formaadis Pred(Suhe, Term1, Term2). Kui programmis on näiteks kirjeldatud predikaat order, võib seda kasutada auastmete järgi järjestamisel:

order(>, rektor, dekaan).
order(>, dekaan, jyri).

order(<, E1, E2):-
order(>, E2, E1),!.

?- predsort(order,[jyri,rektor,dekaan],J).

J = [jyri, dekaan, rektor]

Termi algul (enne avavat sulgu) olevat aatomit nimetatakse peafunktoriks (ülalolevas näites isa). Kogu Prologi interpretaatori töö seisneb tegelikult termide samastamises (unification) - kontrollitakse, kas kaht termi on neis esinevatele muutujatele väärtuste andmisega võimalik samastada. Tavaline võrdusmärk on Prologis samastamine:

?- isa(X,indrek)=isa(andres,Y).
X = andres
Y = indrek
yes

Termide "täht-tähelt" (muutujaid samastamata) võrdlemiseks on operaator ==:

?- X == Y.
no
?- X = 3.
X = 3
yes

Kui term on kujul funktor(Arg1, ...,Argn), on see Swi-Prologi terminoloogias compound (kuid näiteks Amzi-Prologi terminoloogias structure). Struktuuri süntaks on sama, mida järgitakse programmis faktide kirjeldamisel, reegli parem ja vasaku poole osad (komadega eraldatud) on struktuurid jne. Struktuuri süntaksit kontrollib süsteemipredikaat compound(X) :

?- compound(a).
No
?- compound(appi(a)).
Yes

Struktuursete termide (funktor + argumendid) analüüsiks on samuti mitmesuguseid süsteemipredikaate:
- functor(Struktuur,Funktor,Argument) leiab struktuuri Struktuur (pea)funktori Funktor ja struktuuri argumentide arvu:

?- functor(isa(andres,indrek),F,A).
F = isa
A = 2
- arg(N,Struktuur,Argument) leiab struktuuri Struktuur N-da argumendi Argument:
?- arg(2,isa(andres,indrek),A).
A = indrek

- functor(Term, Functor,Arity) leiab termi funktori ja aarsuse (argumentide arvu) või konstrueerib kahest viimasest termi; - arg(N,Term,Väärtus) leiab termi Term N-nda (N peab olema termi Term aarsusest) N-da argumendi väärtuse, näiteks

? - functor(poeg(juhan, jaan), F, A).
F = poeg
A = 2 ;

?- functor(T,poeg,2).
T = poeg(_G322, _G323) ;

?- arg(2,poeg(juhan,jaan),V).
V = jaan ;

Kõige "võimsam" süsteemipredikaat on Term =.. Nimistu, mis muudab termi Term nimistuks [peafunktor,argument1, argument2,...] ja vastupidi:

?- isa(andres,indrek) =..L.
L = [isa, andres, indrek]
?- T =..[tee,tartu,valga].
T = tee(tartu, valga)

Operaatorit =.. kasutatakse sageli uute predikaatide loomiseks programmi töö ajal. Programm võib saada käsu (kas kasutajalt või mingi arvutuse tulemusena) sõnade jadana (näit.: "mine,tuppa"), millest siis moodustatakse täidetav predikaat:

Op =.. [mine,tuppa], call(Op).
Operaatori =.. abil võib kõik programmid taandada vaid ühte predikaati kasutavateks (taandades kõik predikaadid nimistuteks), kasutada muutujaid ka predikaadi nimena jne.

Predikaat var(X) kontrollib, kas muutujal X on juba väärtus (selline kontroll on sageli vajalik enne eituse not kasutamist):

?- var(X).
X = H7
?- X=3,var(X).
no

Küsimused, probleemid: ©2004 Jaak Henno