Centrala begrepp i prolog och logikprogrammering Annamaris lista Databas med fakta och regler: Ett prolog-system består av en databas av fakta, och regler som gäller för dessa fakta. Fakta har formen av predikat, ex. kampart(ninjutsu). Reglerna säger att ett predikat gäller om vissa andra predikat gäller, ex. harvapen(x,y) :- har(x,y), vapen(y). Inference engine avser prologs slutledningsförmåga. Den grundar sig på regeln modus ponens: A -> B A B För att tolka prolog-reglerna enligt denna formel måste du läsa regeln så att den högra sidan av ':-' implicerar den vänstra, alltså har(x,y) & vapen(y) -> harvapen(x,y) (Tänk att du läser ':-' som om eller if.) Negation: true/fail-logik istället för true/false-logik. Prolog-systemet jobbar med en sluten värld ( closed world assumption ). I världen finns bara det som finns i databasen. Om databasen innehåller ex. följande fakta: kampart(ninjutsu). kampart(jujutsu). kampart(aikido). och inga andra fakta eller regler för predikatet kampart, och du ställer frågan?- kampart(karate). så är svaret fail, inte för att karate inte skulle vara en kampart i den verkliga världen, utan för att systemet inte kan bevisa påståendet att karate är en kampart. not (kan även skrivas \+) försöker bevisa sitt argument. Om detta
lyckas, ger not Om det inte lyckas, ger not true: not(q) :- call(q),!, % Om Q kan bevisas, ger not not(q). % Om Q ej kan bevisas, ger not true. Ex. not(harvapen(x,_)) kan ej användas för att generera alla de kamparter som inte har vapen. Så fort systemet hittar ett X som har något vapen bevisas harvapen(x,_) och då ger not(harvapen(x,_)) För att få de önskade svaren måste du ha ett positivt predikat att generera de X som sedan testas för vapen: harejvapen(x) :- kampart(x), not(harvapen(x,_)). Pattern matching eller mönsteranpassning är prologs sätt att hitta de fakta och regler som kan vara relevanta för det aktuella fallet. De flesta regler har flera olika mönster för olika situationer, och prolog tillämpar ALLA som det kan matcha mot det givna målet när det försöker bevisa målet eller hitta alla svaren. (Detta är en väsentlig skillnad till exempelvis SML: där väljer man det första funktionsmönstret som passar, utför den motsvarande funktionskroppen och ignorerar resten.) I prolog använder man sig av varje mönster som passar och försöker bevisa målet enligt de delregler eller delmål som finns på högra sidan av det aktuella mönstret. Reglerna (och mönstren) tillämpas i den ordning de är skrivna. Man kan hindra systemet att använda sig av alla mönstren (och att söka flera bevis eller svar) genom att använda cut-operatorn '!' både i frågor man ställer eller i själva reglerna. Unifiering används för att binda variabler och jämföra datastrukturer. Det är ett exempel på mönsteranpassning: Exempel på unifiering och mönsteranpassning (append är ett färdigtdefinierat predikat som sammanfogar två listor till en tredje lista):?- append([1,2],[3,5,7],x). X = [1, 2, 3, 5, 7]. % definition på append?- append(x,[3,5,7],[1,2,4,3,5,7]). % unifiering vad ska X bindas till? X = [1, 2, 4] ;?- append(x,y,[1,2,4,3,5,7]). X = [], % X matchas mot tomma listan, Y mot listan [1,2,3,3,5,7] Y = [1, 2, 4, 3, 5, 7] ; X = [1], % X matchas mot lista [1], Y mot lista [2,4,3,5,7] etc. Y = [2, 4, 3, 5, 7] ; X = [1, 2], Y = [4, 3, 5, 7] ; X = [1, 2, 4], Y = [3, 5, 7] ; X = [1, 2, 4, 3], Y = [5, 7] ; X = [1, 2, 4, 3, 5], Y = [7] ;
X = [1, 2, 4, 3, 5, 7], Y = [] ; Resolutionsträd eller derivationsträd kan man använda sig av för att förstå prologs sätt att verifiera ett mål. Roten för trädet är målet som ska bevisas, rotens barn är de fakta och regler som gäller för detta mål, och dessa barn i sin tur ger upphov till grenar som motsvarar de delmål som gäller för dessa regler etc. Trädet genomgås med depth-first-strategi när prolog försöker bevisa målet. Se Fishers prolog-tutorial för exempel! Backtracking är prologs sätt att söka efter alternativa bevis eller svar. Om prolog misslyckas med att bevisa ett delmål, tar det ett steg tillbaka och försöker hitta ett annat sätt att bevisa detta mål, med hjälp av nästa regel. Även när beviset lyckas, använder prolog sig av backtracking för att hitta alla svaren till en fråga. Du kan tänka dig backtracking som ett steg tillbaka i depth first-sökningen. Prolog söker efter nya svar och nya bevis tills det har uttömt alla alternativen. Eftersom metoden följer depth-first-strategin, finns det en stor risk av oändlig rekursion om grenen prolog följer slutar i oändlig rekursion, så ordna dina regler noga! Du kan följa prologs backtracking genom att skriva trace. och målet som ska spåras därefter:?- has(jack,x). X = apples ; X = plums ; X = fruits ; % regeln här är has(x, fruits) :- fruit(y), has(x,y). Äpplen räknas. X = fruits ; %... och plommon räknas.?- trace. Unknown message: query(yes) [trace]?- has(jack,x). Call: (7) has(jack, _G313)? creep Exit: (7) has(jack, apples)? creep X = apples ; Redo: (7) has(jack, _G313)? creep Exit: (7) has(jack, plums)? creep X = plums ; Redo: (7) has(jack, _G313)? Creep Call: (8) fruit(_l195)? Creep Exit: (8) fruit(apples)? Creep % gäller... Call: (8) has(jack, apples)? creep Exit: (8) has(jack, apples)? creep Exit: (7) has(jack, fruits)? creep % målet lyckas med X = apples. Finns det annat jack har? % målet lyckas med X = plums. Finns det annat jack har? % Nu har vi inga fler fakta av formen has(jack,något), men vi har regeln % has(x, fruits) :- fruit(y), has(x,y). Nu gäller det alltså att se om has(jack, fruits) X = fruits ; Redo: (8) has(jack, apples)? creep Fail: (8) has(jack, apples)? creep Redo: (8) fruit(_l195)? Creep % Samma sak för plommon som jack har... Exit: (8) fruit(plums)? creep Call: (8) has(jack, plums)? creep Exit: (8) has(jack, plums)? creep Exit: (7) has(jack, fruits)? creep X = fruits ; Redo: (8) has(jack, plums)? creep %... och det gäller för att fruit(apples) gäller och has(jack, apples) gäller. Redo: (8) fruit(_l195)? Creep % Nu försöker systemet ännu se vilka andra frukter det finns som jack potentiellt kunde ha Exit: (8) fruit(bananas)? Creep % fruit(bananas) gäller... Call: (8) has(jack, bananas)? Creep %... men jack har inga bananer. Fail: (8) has(jack, bananas)? creep Redo: (8) fruit(_l195)? creep Exit: (8) fruit(oranges)? Creep % fruit(oranges) gäller... Call: (8) has(jack, oranges)? Creep %... men jack har inga appelsiner.
Fail: (8) has(jack, oranges)? creep % Nu är alternativen uttömda. [debug]?- nodebug. true. Cut-operatorn '!' används för att effektivera sökningarna. Cut kapar av grenar av resolutionsträdet. Ofta vet vi att det bara kan finnas ett enda svar, och då ska vi nöja oss med detta. Med cut ser vi till att prolog inte försöker hitta andra bevis eller ge oss flera svar som inte kan finnas. I andra situationer vill vi effektivera sökningarna, vi kan ex. veta, att reglerna som följer efter en viss regel inte kan ge nya svar, och vi vill inte att prolog försöker tillämpa dem. Cut är dock en del av språket prolog, inte av logikprogrammering, och dess effekt på programverifiering är förödande. Cut i prolog är som goto i imperativ programmering, både när det gäller dess styrka och dess inverkan på programverifiering. Cut hindrar systemet att försöka hitta nya sätt att bevisa målen till vänster om det:?- kampart(x),harvapen(x,y). % utan '!' Y = sabel ; X = ninjutsu,... Y = naginata ; Med cut (observera dess plats och effekten platsen har!!):?- kampart(x),harvapen(x,y),!. Y = sabel. Cut som sista delmål innebär att man inte ska söka efter flera svar till något mål före det. Vi får alltså bara det första svaret.?- kampart(x),!,harvapen(x,y). Y = sabel ;
Cut i mitten säger att vi söker bara ett svar till kampart(x), regeln före cut, men alla svaren till harvapen(x,y), regeln efter cut. Exempel på cut i själva reglerna: add_to_set(x, S, S) :- member(x, S),!. % the element is in the set already add_to_set(x, S, [X S]). Om elementet X redan finns i mängden S ska det inte sättas dit, för mängden tillåter inte duplikat. '!' i den första regeln ser till att man inte försöker bevisa member(x, S) på flera sätt inte försöker tillämpa en annan regel för add_to_set.?- add_to_set(3, [1,3], X). X = [1, 3].?- trace. Unknown message: query(yes) [trace]?- add_to_set(3, [1,3], X). Call: (7) add_to_set(3, [1, 3], _G320)? creep Call: (8) member(3, [1, 3])? creep Call: (9) member(3, [3])? creep Exit: (9) member(3, [3])? creep Exit: (8) member(3, [1, 3])? creep Exit: (7) add_to_set(3, [1, 3], [1, 3])? creep X = [1, 3]. Prologs deklarativa karaktär innebär att man ponerar fakta och regler, som gäller för dessa fakta. Det finns ingen skillnad på inparametrar, utparametrar eller resultat, utan samma faktum eller samma regel kan användas på flera sätt: harstammarfran(kungfu, kina). harstammarfran(kobudo, okinawa). harstammarfran(karate, okinawa). harstammarfran(sumo, japan). harstammarfran(ninjutsu, japan). Frågan harstammarfran(x, japan) kan användas för att veta vilka grenar som härstammar från japan. Frågan harstammarfran(kobudo,x) kan användas för att ta reda på var kobudo härstammar från. Frågan harstammarfran(x,y) kan användas för att ta reda på allt som härstammar från något inom de kunskapsgränser som vår lilla databas har.