Toto o tom pisou autori:
Prolog is a simple but powerful programming language developed
at the University of Marseilles [Roussel 75], as a practical tool for
programming in logic [Kowalski 74] [van Emden 75] [Colmerauer 75].
>From a user's point of view the major attraction of the language is
ease of programming. Clear, readable, concise programs can be
written quickly with few errors.
(nezni to pekne?)
Rozhodl jsem se pro zmenu nezacit teorii ale jit hned na vec. Napisem takovou malou adventurku, kde by clovek mohl chodit a neco delat. Proste hru minimalne urovne pomsty sileneho ataristy. Na ni bych chtel ukazat vyhody tohoto noveho pristupu. Nepokousejte se zatim nic do prologu busit jenom se zamyslete, co vsechno nam tento postup usetril. (jeji kompletni zdrojaky ,ukazku funkcnosti a popis co jsme vlastne delali pridam na konec.)
popis seznam predmetu seznam dalsich mistnosti, kam se da jit.Kdybyste to delali v cecku vypadalo by to asi takto:
struct mistnost { char *name struct predmet *predmety[MAXPREDMET]; struct mistnost *dalsi[MAXMISTNOST]; }No a ted si prestavte to dynamicky alokovani, nacitani z datovyho fajlu, implementaci funkce jdi, ktera musi cely listing dalsi prohledat a porovnat. Navrh struktury datovyho fajlu-jak budete delat ukazatele kdyby fajl vypadal treba jako:
Mistnost:Zachod Popis:Jsi na zachode...Muzes jit na chodbu nebo oknem ven na zahradu Predmety:Papir Cesty:chodba,zahradaA ted to nacist..vyznat se v nem,kdyz nactete cesty:chodba musite si to napred pamatovat jako string a teprve casem-az bude cela databaze v pameti bude mozne nahradit slova ukazatelama-tudiz musite pridat dalsi kolomky do struktury misnost, ktere budou bezne nepouzity. A cely dohromady by to stalo za nic protoze:
(location "papir" "zachod") (connect "zachod" "zahrada") (description "zachod" "Jsi na zachode...Muzes jit na chodbu nebo \ oknem ven na zahradu")A bylo by to relativne jednoduche a eleganti a bez omezeni(snad mimo tluceni se jmen mistnosti z funkcema-musely by se pravdepodobne nazvy delat stylu room-[jmeno]) O to by se uz starala nejaka funkce ktera by se volala nejak: (roomsymbol "blbec")
=> ROOM_BLBECTo je uz lepsi..Ale jak by to delal prolog?
Proste nijak. Jednoduse byste mu napsali:
|: location(papir,zachod). |: connect(zachod,zahrada). |: connect(zachod,chodba).A protoze cesta chodba->zachod je dvousmerna:
|: connect(chodba,zachod).A mate to! bludiste je v pameti! Jak rychle jak efektivne a skvrna od tresni je pryc!
?- location(papir,X). X=zachod ?- connect(zachod,X). X=zahrada X=chodbaNo monkey island to jeste neni ale pametove ulozeni mame a po dvou minutach! Omezeni jsou tu nulovy..format vstupniho fajlu je taky slusny..detekce syntatickych chyb automaticka(interpretrem).
(let (a (member (roomsymbol mistnost) (mistnost-cesta you)) ( (if (eq a nil) (print "tam jit nemuzes") (setq you (car a)))) (nejsem si zcela jist jestli je spravne..kdyz ne tak me omluvte)a v prologu takto:
jdi(X) :- %funkce na chozeni you(L), %do L ulozi vasi pozici connect(L,X),%zjisti jestli vede cesta z L do X-zde muze provadeni selhat retract(you(L)), %Zrusi spojeni ze starou mistnosti assert(you(X)). %Zavede do databaze novou asociaci-you a nova mistnost jdi(X):- %V pripade ze cesta nevede-prvni jdi selhalo write(' Tam to nejde. '),nl.Tomu uz asi moc nerozumite..ale verte ze to je zcela jednoducha konstrukce.
smrt :- you(L), retract(you(L)), assert(you(nebe)). akce :- location(papir,zahrada), retract(location(papir,zahrada)), write(' Vrhla se na tebe diva duchodkyne z holi a utloukla te ze slovy'),nl, write(' Takhle znecistovat zivotni prostredi! Kam se ten svet riti! '), smrt, akce. akce :- nl.Tato akce zjisti jestli je papir na zahrade,vynada vam a pomoci jednoducheho predikatu smrt vas presune do nebe. Nakonec se rekurzivne zavola to je pro situaci kdyby takovych akci bylo v programu vic.
A ted staci pridat par jednoduchych prikazu a nejkay user interface A
monkey island II je na svete! Nebylo to snadny? Tady je videt rozdil
mezi strukturovanym a logickym programovanim. Nerikam ze se prolog hodi
na vsechno ale na neco je strasne sikovnej=jen se kouknete kolik nam to
usetrilo prace! Jak nas to nutilo udelat ten program pekny, citelny
snadno rozsiritelny. Jak jsou reseni v prologu elegantni,
jednoducha....kolik to usetrilo prace.
A tvrde na vec
(ted je cas nastartovat prolog prikazem pl-distribuci najdete na kazdem
mirroru slakwaru v adresari devel/lang/prolog/swi)
Co jsme vlastne psali?
Atomy
Stejne jako lisp ma i prolog atomicky typy.. To jsou cisla:
555 -555 2`1110 je ve dvojkove soustave 6`1323 je v sestkovekonstanty:
predikat(parametry)tedy treba:
blbec(x,y)To vytvori jakesi spojeni mezi symboly. Da se pomoci toho moc pekne delat ruzne diagramy(treba bludiste v nasi hre) Nebo asociativni site: barva(medved,cerveny) a potom se muzete ptat jakou barvu ma medved. Da se i retezit:
s(np(john),vp(v(likes),np(mary)) Vytvori takove spojeni: s / \ np vp | / \ john likes maryZ nasi hry znate:
location(papir,zachod)(abyste mohli zadavat clausy ze vstupu musite napsat [user]. - prompt se zmeni na |: a muzete je zadat. po ctrl+d se zase dostanete zpet Je tu ale problem protoze po kazdem zadani [user] se stare vazby zrusi. Proto je lepsi priklady zapisovat do fajlu a potom [fajl] je nahrat do interpretru obsah fajlu z priklady dam na konec)
nebylo volani funkce, ktera by naplnila nejakou strukturu ale jednoduse si prolog zapamatoval neco takoveho:
papir | ma location | v zachod(Takhle se casto zakresluji clausy z dvema parametrama-u tri uz kresleni drhne)
Kdybychom napsali:
umisteni(papir,wc)rozsirili bychom jeho tabulku na:
papir | | location umisteni | | v v zachod wcTo prvni slovo je (location,umisteni) je predikat. Zbyle jsou jeho parametry. Trochu to pripomina volani funkce. Muzou byt predikaty bez parametru:
smrt.nebo z jednim:
barva(zelena). barva(cervena). zelena< -----barva----> cervenaa i vice. To se uz celkem spatne maluje do site. Takovymto jednoduchym zpusebem muzeme vystavet libovolnou asociativni sit. Cely program v prologu je do teto site zabudavan tak, ze muzeme udelat asociace, ktere se nevi predem, ale musi se vyvolat nejaky dalsi kod ktery urci jestli spojeni existuje nebo ne. Vytvareni takovych asociaci popisu pozdeji. Cely prologacky program je tedy soubor pravidel, jak neco ma vypadat a prolog se snazi jim vyhovet.
Bezne pouzivane konstrukce v prologu jsou listy:
. / \ 1 . / \ 2 . / \ 3 []Tato konctrukce ma zapis:
.(1,.(2,.(3,[])))SAmozdrejme ze existuje i pohlednejsi zapis
[1,2,3]A konstrukce:
. / \ a . / \ b Ljde zapsat
[a,b|L]Stringy jsou listy integeru z ascikodama.
?- [clause]System potom zacne prochazet celou databazi z pokousi se najit dotazovane zpojeni. Muzeme tedy zadat
?- connect(zachod,zahrada).A system odpovi yes protoze spojeni existuje. Jde tu ale jedna moc zajimava vec:
?- connect(zachod,X).System zjisti ze X je promena..pokusi se proto v databazi najit nejakou dobrou hodnotu prot promenou. Mame zpojeni mezi zachodem a zahradou napise tedy:
X = zahradaa ceka. Pokud zmacknete enter system usoudi ze nasel zpravne a promenou tak nastavi-v dalsi otazce uz bude X vzdycky jenou zahrada. Ale kdyz zmacknete ; system bude hledat dal..a najde jeste jedno spojeni na chodbu a opet se zepta. Kdyz znova zmacknete strednik nic nenajde a napise no.(Pri experimentech je dobre vzdycky mackat stednik protoze jinak se promena na neco nastavi a vy si musite vyprat nejake jine pismeno pro dalsi testovani)
Muzeme pouzivat i predikaty ktere jsme nezadali:
?- member(b,[a,b,c]).[a,b,c] je list. Member se pta jestli b je jeho prvkem. System tedy odpovi:
yesToto je priklad vyvolavani predikatu a dotazovani se na asociaci, ktera se predem nevi-system nema u vsech prvku listu asociaci member ale member je nejaky kus kodu, co to urci. Tedy neco jako funkce, ktera vraci boolean.
Zajimave ale je ze i member muzeme volat z promenou:
?- member(X,[a,b,c]).Tohle vypada dost nesmyslne-v normalnim jazyce by to proste napsat neslo. Ale v prologu ano. System najde prvni moznost jak by to mohlo souhlasit tedy a. A celkem spravne se zepta:
X = aA vy muzete odpovedet-enter znamena ano to je spravne
; znamena ne..hledej dal..zepta se tedy na bTady je uz jasny jak funguje.
Otazky muzeme retezit za sebe:
?- connect(zachod,X),connect(zahrada,X) a vysledky se budou pekne predavat..
:- member(3,[1,2,3]), write(ok).Napise ok protoze 3 je clenem posloupnosti. Provadeni podminky je podobne jako u otazky z tim rozdilem, ze jeji provadeni se preusi jekamile nejaky dotaz odpovi no. Tedy kdyby volani member vratilo no k volani ok by uz nikdy nedoslo. Zaroven write je priklad jak muze predikat suplovat funkci.
(V zadavacim modu) zamestnany(X) :- zamestnava(X,Y)(pise se to stejne jako normalni clausa:location(zahrada,zachod) az na to ze misto konstant tam date promene a pak napisete nejake pravidlo) Znamena to neco jako:
zamestany(X) :- podnikatel(X)To znamena ze clovek je zamestnany take pokud je podnikatel-podnikatele nikto nezamestnava. Potom kdyz zadame:
?- zamestnany(novak).System se nejprve podiva jestli je nejake spojeni zamestnava(novak,Y) a pokud ne zjisti jestli je v databazi podnikatel(novak) pokud ani to ne odpovi no.
Takove predikaty jsou pro prolog vsim. Cely program se pomoci nich pise: Tady je vysvetleni na konstrikci:
jdi(X) :- you(L), connect(L,X), retract(you(L)), assert(you(X)). jdi(X) :- write(' Tam to nejde. '),nl.Kdyz se clovek zepta:
?- jdi(zahrada)Provede kod predikatu-to co nasleduje.Tedy podminku. V teto podmince nejprve pomoci:
you(L),Ulozi do L tvoji pozici. To by selhat nemelo. Jedine v tom pripade ze by you zadnou pozici nemel a tak by to byla interni chyba programu.
connect(L,X)To je otazka jestli existuje spojeni mezi mistnosti kde se zrovna nachazite a X-kam chcete jit. Pokud toto zpojeni neexistuje prerusi se provadeni a jdi selze. Pokud k tomu preruseni dojde zkusi se dalsi mozny predikat,ktery je nadefinovan a tim je:
jdi(X) :- write(' Tam to nejde. '),nl.A tan spravne vypise chybu. Pokud ale zpojeni existuje pokracuje provadeni radkem:
retract(you(L)),To je volani predikatu, ktery je zabudovan primo do prologu a zrusi platnost closy v jeho parametru. Tedy zrusi spojeni mezi vami a starou mistnosti. Dalsi radek:
assert(you(X)),Zavede novou closu do databaze tentokrat spojeni mezi vami a novou mistnosti. A posledni radek:
write(' jses na '),write(X),nl.
cesta(X,Y,Z) :- connect(X,Y), connect(Y,Z).Toto probehne pouze kdyz jsou za sebou. V nasem bludisti to plati pouze pro kombinaci:
?- cesta(chodba,zachod,zahrada). odpovi yes.A k cemu by nam byla takova funkce v normalnim jazyce? K nicemu! To ale neplati v prologu! Zadejte treba:
?- cesta(chodba,Y,zahrada).A co se nestane? system odpovi:
Y = zachod yesStalo se neco zvlastniho....System zauvazoval a nasel spravne reseni. V nasem pripade to je jasne. Predikat se interpretroval jako:
?- connect(zahrada,Y) Y = zachod ?- connect(zachod,chodba) yesA nastavilo se tedy Y. Ale co kdybychom udelali dalsi spoje:
|:connect(zahrada,chodba). |:connect(chodba,zahrada).Nyni uz situace neni jednoznacna. bludiste vypada:
/--------zachod< ---> chodba | ^ Zahrada< -----------------/A presto system stale spravne odpovida zachod. To je prave podstata logickeho programovani. My jsme napsali pravidlo a system hleda odpoved tim ze zkousi dosadit vsechny mozne postupy a to dela tak dlouho dokud nenajde odpoved na tu se vas prepta a pokud nevyhovi pokracuje v hledani. Prave teto metode se rika backtracing. To znamena ze klidne muzeme zadat:
?- cesta(X,Y,zahod).Tedy ptame se systemu odkud se muzeme dostat pomoci dvou tahu na zachod? A system nam odpovi?
X = zahrada Y = chodbaa ceka. Pokud zmackeneme enter uz dal nehleda ale pokud dame ; klidne napise:
X = chodba Y = zahradaPokud opet odmitneme odpovi ze uz dalsi reseni nezna. To mi pripomelo davida kaprfilda(nebo jak se pise) a jeho oblibena slova:budu carovat s vami...dejte si prst na obrazovku a vyberte si ctverecek a udelejte tri tahy uhlopricne nebo rovne. A vim ze nejste ve vagone..... Najit takovy postup je celkem slozite ale v prologu by se to psalo jedna basen... Proste by jste si udelali predikaty na posouvani a potom by sjte udelali dotaz kdy predikat najdi_cestu_na_X(X,Y) je nula... Psat neco takoveho v konvencnim jazyce by bylo utrpeni.
Dalsi sikovne vyuziti bactracingu je to,ze muze simulovat smycky. Napriklad kdyz chceme vypsat vsechny cesty z mistnosti muzeme udelat:
cesty :- you(L), connect(L,X), write(X), write(' '), fail. cesty :- nl.A system napred bude chtit vyhovet prvnimu predikatu. Ten funguje takto:
go :- write('cesty:'), cesty, write('>> '), read(X), call(X), akce, go.Podobne se necha udelat take seznam predmetu v mistnosti a inventar.
Tim jsem snad objasnil zaklady prologu. Je cas ukazat zdrojaky.
%Umistime predmety location(papir,zachod). % Bludiste connect(zachod,zahrada). connect(zachod,chodba). connect(chodba,zachod). connect(zahrada,chodba). connect(chodba,zahrada). % Popisy pro jednotlive mistnosti describe(papir,'proste toaletak'). describe(zachod,'Jdi na zachode..nelibe to tu voni'). describe(zahrada,'Jsi na zahrade..ptaci zpivaji, slunce sviti...'). describe(chodba,'Jsi na chodbe..vede ze zachodu na zahradu'). % Predikaty co delaji smycky prez backtracing cesty :- %vypise vsechny cesty s mistnosti you(L), %ulozi do L vasi pozici connect(L,X), %zjisti cestu z L a do X ulozi cil-tady se dela backtracing write(X), %vypise cilovou mistnost write(' '), %oddeli slova fail. %selze,aby se system pokusil najit dalsi cestu a take ji vypsal cesty :- %tohle se vyvola,kdyz uz neni zadna cesta k vypsani nl. %novy radek-konec vypisu vydis :- %vypise predmety v mistnosti you(L), %ulozi do L vasi pozici location(X,L),%najde predmet ktery je v mistnosti L-tady se dela backtracing write(X), %vypise nalezeny predmet write(' '), %oddeli slova fail. %selze vydis :- %tohle se vyvola kdyz uz neni dalsi predmet v mistnosti nl. inventar :- %vypise,co uzivatel nese-funguje stejne jako vidis ale location(X,you), %misto mistnosti testuje you write(X), write(' '), fail. inventar :- nl. popis :- %vypise popis mistnosti you(L), %do L ulozi vasi pozici describe(L,X),%vyhleda popis mistnosti write(X),nl. %a vypise % Jednoduche predikaty na modifikaci herni databaze jdi(X) :- %funkce na chozeni you(L), %do L ulozi vasi pozici connect(L,X),%zjisti jestli vede cesta z L do X-zde muze provadeni selhat retract(you(L)), %Zrusi spojeni ze starou mistnosti assert(you(X)). %Zavede do databaze novou asociaci-you a nova mistnost jdi(X):- %V pripade ze cesta nevede-prvni jdi selhalo write(' Tam to nejde. '),nl. smrt :- %Tento predikat vas zabije you(L), %Do L ulozi vasi pozici retract(you(L)), %Zrusi spoj ze starou mistnosti assert(you(nebe)). %Presune vas do nebe akce :- %Tyto predikaty ridi vsechny udalosti,ktere dejou ve hre location(papir,zahrada), %Pokud jste vyhodili papir na zahradu retract(location(papir,zahrada)),%Zrus spojeni-aby se zabranilo zacykleni write(' Vrhla se na tebe diva duchodkyne z holi a utloukla te ze slovy'),nl, write(' Takhle znecistovat zivotni prostredi! Kam se ten svet riti! '), smrt, %Zabyje vas akce. %Spusti se znova rekurzivne.Touto konstrukci se zarizuje to, %aby bylo mozne takovych akci udelat vic. Proste se pisou za %sebe. Kdyz tento predikat probehne,automaticky zrusi podminky %aby mohl probehnout znova-zrusi papir. Pri dalsim provadenim %selze,vyvola se tedy dalsi mozna situace-dalsi akce ve hre akce :- %Toto je posledni z akci-nic se nestalo. To je proto,aby %volani akce z go neselhalo a nezrusilo tim provadeni cele hry nl. done :- %Tento predikat zjistuje jestli hra nezkoncila you(nebe), write(' Sice jste zemrel ale nevadi...vlastne sjte hru vyhral '). %Hlavni smycka go :- done. %Timto je zaruceno aby hra zkoncila v pripade, ze done zjisti %ze je konec-provede se tato prvni moznost go a ukonci se go :- %Jinak se pokracuje na toto go popis, %Vypise popis mistnosti write('cesty:'),%Vypise vesty z mistnosti cesty, write('vydis:'),%Vypise co vidis vydis, write('>> '), %Prompt read(X), %Nacte closu z klavesnice call(X), %Provede nactenou closu akce, %Akce-tedy duchodkyne go. %Smycka pomoci rekurze %Uzivatelske prikazy zvedni(X) :- %Zvedne predmet you(L), location(X,L),%Zjisti jestli predmet X je v mistnosti L-zde muze selhat retract(location(X,L)),%Zrusi spojeni predmetu z mistnosti assert(location(X,you)),%Zavede zpojeni z vami write(' Zvedl jsi '),write(X),nl.%A hlaska zvedni(X) :- %Pro pripad selhani write(X),write(' tu nikde neni'). poloz(X) :- %Polozi predmet location(X,you), you(L), retract(location(X,you)), assert(location(X,L)), write(' Polozil jsi'),write(X),nl. poloz(X) :- write(X),write(' u sebe nemas'). %Predikaty z vykladu.. cesta(X,Y,Z) :-%Typicky priklad backtracingu connect(X,Y), connect(Y,Z). you(zachod). % Priklad matematiky v prologu mul(R1,I1,R2,I2,R3,I3):-%Nasobeni komplexnich cisel R3=R1*R2-I1*I2, I3=R1*I2-R2*I1.
1 ?- [pll]. [WARNING: (/root/pll:41) Singleton variables: X] pll compiled, 0.03 sec, 6,300 bytes. Yes 2 ?- go. Jdi na zachode..nelibe to tu voni cesty:zahrada chodba vydis:papir >> |: zvedni(papir). Zvedl jsi papir Jdi na zachode..nelibe to tu voni cesty:zahrada chodba vydis: >> |: jdi(chodba). Jsi na chodbe..vede ze zachodu na zahradu cesty:zachod zahrada vydis: >> |: inventar. papir Jsi na chodbe..vede ze zachodu na zahradu cesty:zachod zahrada vydis: >> |: poloz(papir). Polozil jsipapir Jsi na chodbe..vede ze zachodu na zahradu cesty:zachod zahrada vydis:papir >> |: zvedni(papir). Zvedl jsi papir Jsi na chodbe..vede ze zachodu na zahradu cesty:zachod zahrada vydis: >> |: jdi(zahrada). Jsi na zahrade..ptaci zpivaji, slunce sviti... cesty:chodba vydis: >> |: >> |: poloz(papir). Polozil jsipapir Vrhla se na tebe diva duchodkyne z holi a utloukla te ze slovy Takhle znecistovat zivotni prostredi! Kam se ten svet riti! Sice jste zemrel ale nevadi...vlastne sjte hru vyhral Yes
priklady funkce bactracingoveho predikatu cesta
3 ?- cesta(X,Y,zachod). X = zachod Y = chodba ; X = zahrada Y = chodba ; No 4 ?- cesta(X,zachod,Y). X = chodba Y = zahrada ; X = chodba Y = chodba ; No 5 ?- cesta(zachod,X,Y). X = zahrada Y = chodba ; X = chodba Y = zachod ; X = chodba Y = zahrada ; No 6 ?- cesta(zachod,X,zachod). X = chodba ; No
Trocha matematiky
7 ?- mul(1,2,3,4,I,R). I = 1 * 3 - 2 * 4 R = 1 * 4 - 3 * 2 ; No Vyrazy se vyhodnocuji az kdyz je treba 8 ?- mul(1,2,I,R,I1,R1). I = G792 R = G796 I1 = 1 * G792 - 2 * G796 R1 = 1 * G796 - G792 * 2 ; No Z backtracingem v matematice je to uz horsi.....
Taky jsem zaradil prolog mezi lispovsky jazyky. To je proto ze stejne jako lisp ma udelane promene, trypy(atomicke atd..) ma podobne rozdeleni interpretru-proto read(X) necetlo string ale celou clausu protoze i v prologu se clause da ulozit do premene. Ma dynamicke usporadani pameti a hodne dalsich spolecnych rysu. Proto existuje hodne prologackych interpretru napsanych v lispovi.
Tento soubor je soucasti rozsahle sbirky skolicek na http://www.ucw.cz/~hubicka/skolicky
Take si muzete prohlidnout jeji puvodni textovou podobu
Nebo mi mailnout na hubicka@ucw.cz
Copyright (C) Jan Hubicka 1995