Prolog 是一種與眾不同的語言,不用來開發軟體,專門解決邏輯問題。比如,"蘇格拉底是人,人都會死,所以蘇格拉底會死"這一類的問題。
Prolog 就是"邏輯程式設計"(programming of Logic)的意思。只要給出事實和規則,它會自動分析其中的邏輯關係,然後允許使用者透過查詢,完成複雜的邏輯運算。
本文簡單介紹如何使用 Prolog 語言,主要參考了 xmonader 的教程。
一、SWI-Prolog
學習之前,請安裝 Prolog 的執行環境 SWI-Prolog,才能執行後面的程式碼。
SWI-Prolog 官網有各個作業系統的二進位制安裝包,下載即可。Debian / Ubuntu 系統還可以用下面的命令。
$ sudo apt-get install swi-prolog
安裝以後,Linux 系統可以命令列啟動。
$ swipl ?-
然後,就進入了 Prolog 執行環境,?-
是命令提示符。下面是 Hello world 的例子。
?- write("Hello, world"). Hello, world! true.
上面命令輸出 Hello world。
有幾個地方需要注意。Prolog 所有語句的結尾都用一個"點"(.
)表示結束。write()
是列印命令。命令本身就是一個表示式,輸出完成以後,返回值就是true.
,也會顯示出來。
如果想在 Hello world 之間插入一個換行,可以使用nl
命令。
?- write('Hello,'), nl, write('world'). Hello, world true.
退出 SWI-Prolog,可以使用halt
命令,別忘了後面還要加一個點。
?- halt.
二、基本語法
2.1 常量和變數
Prolog 的變數和常量規則很簡單:小寫字母開頭的字串,就是常量;大寫字母開頭的字串,就是變數。
?- write(abc). abc true. ?- write(Abc). _3386 true.
上面程式碼中,abc
是常量,輸出就是自身;Abc
是變數,輸出就是該變數的值。
2.2 關係和屬性
兩個物件之間的關係,使用括號表示。比如,jack 的朋友是 peter,寫成friend(jack, peter).
。
注意,jack 的朋友是 peter,不等於 peter 的朋友是 jack。如果兩個人都認為對方是朋友,要寫成下面這樣。
friend(jack, peter). friend(peter, jack).
如果括號裡面只有一個引數,就表示物件擁有該屬性,比如 jack 是男性,寫成male(jack).
。
2.3 規則
規則是推理方法,即如何從一個論斷得到另一個論斷。
舉例來說,我們定下一條規則:所有朋友關係都是相互的,規則寫成下面這樣。
friend(X, Y) :- friend(Y,X).
上面程式碼中,X
和Y
都是大寫,表示這是兩個變數。符號:-
表示推理關係,含義是隻要右邊的表示式friend(Y, X)
為true
,那麼左邊的表示式friend(X, Y)
也為true
。因此,根據這條規則,friend(jack, peter)
就可以推理得到friend(peter, jack)
。
如果一條規則取決於多個條件同時為true
,則條件之間使用逗號分隔。
mother(X, Y) :- child(Y,X), female(X).
上面程式碼中,X
是Y
的母親(mother(X, Y)
)取決於兩個條件:Y
是X
的小孩,X
必須是女性。只有這兩個條件都為true
,mother(X, Y)
才為true
。
如果一條規則取決於某個條件為false
,則在條件之前加上\+
表示否定。
onesidelove(X, Y) :- loves(X, Y), \+ loves(Y,X).
上面程式碼中,X
單相思Y
,取決於兩個條件。第一個條件是X
喜歡Y
,第二個條件是Y
不喜歡X
。
2.5 查詢
Prolog 支援查詢已經設定的條件。我們先寫一個指令碼hello.pl
。
friend(john, julia). friend(john, jack). friend(julia, sam). friend(julia, molly).
然後在 SWI-Prolog 裡面載入這個指令碼。
?- [hello]. true.
上面程式碼中,true.
是返回的結果,表示載入成功。
然後,可以查詢兩個人是否為朋友。
?- friend(john, jack). true. ?- friend(john, sam). false.
listing()
函式可以列出所有的朋友關係。
?- listing(friend). friend(john, julia). friend(john, jack). friend(julia, sam). friend(julia, molly). true.
還可以查詢john
有多少個朋友。
?- friend(john, Who). Who = julia ; Who = jack.
上面程式碼中,Who
是變數名。任意的變數名都可以,只要首字母為大寫。
三、地圖著色問題
下面看看 Prolog 如何解決實際問題。
我們知道,地圖的相鄰區域不能使用同一種顏色。現在有三種顏色:紅、綠、藍。請問如何為上面這幅地圖著色?
首先,定義三種顏色。
color(red). color(green). color(blue).
然後,定義著色規則。
colorify(A,B,C,D,E) :- color(A), color(B), color(C), color(D), color(E), \+ A=B, \+ A=C, \+ A=D, \+ A=E, \+ B=C, \+ C=D, \+ D=E.
上面程式碼中,colorify(A,B,C,D,E)
是一個對 ABCDE 五個變數求值的表示式。該表示式為true
的條件是,這五個變數各自為一種顏色,則相鄰的變數不相等。
最後,這兩段程式碼合在一起,組成一個指令碼map.pl
,再載入這個指令碼。
?- [map]. true.
執行表示式colorify(A,B,C,D,E)
,SWI-Prolog 就會將三種顏色依次賦值給變數,測試哪些組合是可能的結果。
?- colorify(A,B,C,D,E). A = red, B = D, D = green, C = E, E = blue; A = red, B = D, D = blue, C = E, E = green ; A = green, B = D, D = red, C = E, E = blue ; A = green, B = D, D = blue, C = E, E = red ; A = blue, B = D, D = red, C = E, E = green ; A = blue, B = D, D = green, C = E, E = red ;
可以看到,計算機給出了6組解,即有6種可行的地圖著色方法。
四、誰是兇手
下面看一個比較有趣的邏輯題。
Boddy 先生死於謀殺,現有六個嫌疑犯,每個人在不同的房間,每間房間各有一件可能的兇器,但不知道嫌疑犯、房間、兇器的對應關係。請根據下面的條件和線索,找出誰是兇手。
已知條件:六個嫌疑犯是三男(George、John、Robert)三女(Barbara、Christine、Yolanda)。
man(george). man(john). man(robert). woman(barbara). woman(christine). woman(yolanda).
為了後面解題的方便,需要把"男人"和"女人"都定義為"人"。
person(X):- man(X). person(X):- woman(X).
六個嫌疑犯分別待在六個房間:浴室(Bathroom)、飯廳(Dining Room)、廚房(Kitchen)、起居室(Living Room)、 儲藏室(Pantry)、書房(Study)。每間房間都有一件可疑的物品,可以當作兇器:包(Bag)、火槍(Firearm)、煤氣(Gas)、刀(Knife)、毒藥(Poison)、繩索(Rope)。
location(bathroom). location(dining). location(kitchen). location(livingroom). location(pantry). location(study). weapon(bag). weapon(firearm). weapon(gas). weapon(knife). weapon(poison). weapon(rope).
下面宣告一條規則,每個房間的人都是不一樣的。
uniq_ppl(A,B,C,D,E,F):- person(A), person(B), person(C), person(D), person(E), person(F), \+A=B, \+A=C, \+A=D, \+A=E, \+A=F, \+B=C, \+B=D, \+B=E, \+B=F, \+C=D, \+C=E, \+C=F, \+D=E, \+D=F, \+E=F.
然後,定義一個表示式murderer(X)
,變數X
就是兇手。該表示式只有滿足以下所有條件,才可能為true
。
murderer(X) :- uniq_ppl(Bathroom, Dining, Kitchen, Livingroom, Pantry, Study), uniq_ppl(Bag, Firearm, Gas, Knife, Poison, Rope),
注意,上面程式碼中Bathroom
和Bag
這樣的字串,都是大寫字母開頭,所以都是變數,代表對應的人。至於具體是誰,就要透過推理得到。
線索一:廚房裡面是一個男人,那裡的兇器不是繩索、刀子、包和火槍。
man(Kitchen), \+Kitchen=Rope, \+Kitchen=Knife, \+Kitchen=Bag, \+Kitchen=Firearm,
線索二:Barbara 和 Yolanda 在浴室和書房。
woman(Bathroom), woman(Study), \+christine=Bathroom, \+christine=Study, \+barbara=Dining, \+barbara=Kitchen, \+barbara=Livingroom, \+barbara=Pantry, \+yolanda=Dining, \+yolanda=Kitchen, \+yolanda=Livingroom, \+yolanda=Pantry,
線索三:帶包的那個人不是 Barbara 和 George,也不在浴室和飯廳。
\+barbara=Bag, \+george=Bag, \+Bag=Bathroom, \+Bag=Dining,
線索四:書房裡面是一個帶繩子的女人。
woman(Rope), Rope=Study,
線索五:起居室裡面那件兇器,與 John 或 George 在一起。
man(Livingroom), \+Livingroom=robert,
線索六:刀子不在飯廳。
\+Knife=Dining,
線索七:書房和食品儲藏室裡面的兇器,沒跟 Yolanda 在一起。
\+yolanda=Pantry, \+yolanda=Study,
線索八:George 所在的那間屋子有火槍。
Firearm=george,
線索九:Boddy 先生死在食品儲藏室裡,那裡的兇器是煤氣。
Pantry=Gas, Pantry=X, Gas=X,
線索就是上面這些,然後把寫好的所有表示式放在一起,組成一個完整的指令碼crime.pl
,程式碼看這裡。
載入這個指令碼,執行murderer(X)
函式,由於條件複雜,運算時間較長,最終會顯示兇手是誰。
?- [crime]. true. ?- murderer(X). KILLER IS :christine Bathroom: yolanda Dining: george Livingroom: john Pantry: christine Study: barbara Kitchen: robert Knife: yolanda Gas: christine Rope: barbara Bag: john Poison: robert Firearm: george X = christine ;
(完)