2012-07-23 54 views
6

我想了解爲什麼Prolog實現不按照教科書中的執行模型行事 - 例如,Sterling和Shapiro的「The Prolog of Prolog」一書(第6,「Pure Prolog」,第6.1節,「Prolog的執行模型」)。Prolog中的雙重否定和執行模型

我所指的執行模型是這樣的(第93頁斯特林&夏皮羅的):

輸入:一個目標G和程序P

輸出: G的實例爲P的邏輯結果,或不以其他方式

算法:

Initialize resolvent to the goal G 
while resolvent not empty: 
    choose goal A from resolvent 
    choose renamed clause A' <- B_1, ..., B_n from P 
      such that A, A' unify with mgu θ 
     (if no such goal and clause exist, exit the "while" loop) 
    replace A by B_1, ..., B_n in resolvent 
    apply θ to resolvent and to G 
If resolvent empty, then output G, else output NO 

此外(同一本書的第120頁),Prolog按照從左到右的順序選擇目標(choose goal A),並按它們在程序中出現的順序搜索子句(choose renamed clause ...)。

下面的程序定義了not(在程序中稱爲n)和一個單一的事實。

n(X) :- X, !, fail. 
n(X). 

f(a). 

如果我試圖證明n(n(f(X))),它成功(根據兩本教科書,並在SWI序言,序言GNU和邑)。但這不是很奇怪嗎?根據該執行模式,這幾本書揭露,這是我希望發生的(跳過的變量重命名讓事情變得簡單,因爲不會有什麼衝突呢):

  • 預解:n(n(f(Z)))

  • 統一匹配X第一個子句與n(f(Z)),並用該子句的尾部替換目標。

  • 決議:n(f(Z)), !, fail

  • 統一再次X匹配與f(Z)第一條中,並與第

  • 解決對策尾部取代瞭解決方法的第一個目標:f(Z), !, fail, !, fail

  • 統一匹配f(Z) - >成功!現在,這從解決方案中消除了。

  • 決議:!, fail, !, fail

和 「!, fail, !, fail」 應該成功!裁員後失敗。故事結局。 (事實上​​,輸入!,fail,!,fail作爲查詢將在我有權訪問的所有Prolog系統中失敗)。

所以我可以假定教科書中的執行模型並不完全是Prolog使用的模型?

編輯:將第一個子句更改爲n(X) :- call(X), !, fail在我嘗試的所有Prolog中沒有任何區別。

回答

4

當你到達最後一步:

  • 預解:!,失敗,失敗

!這裏的意思是,「抹去一切」。所以解決方法變得空虛。 (當然這是假的,但足夠接近)。削減在這裏根本沒有意義,第一個fail說翻轉決定,第二個fail翻轉它。現在解決方案是空的 - 決定是「是」,並保持如此,兩次翻轉。 (這也是僞造它 ...「翻轉」只有在回溯存在時纔有意義)。

當然,你不能在解決方案的目標列表中放置一個減號!,因爲它不僅僅是要實現的目標之一。它有一個運作意思是,它通常說「停止嘗試其他選擇」,但這個解釋器保持沒有軌跡任何選擇(它「好像」一次做所有的選擇)。 fail不僅僅是一個要實現的目標,它還表示「你已經成功說出你沒有,反之亦然」。

因此,我可以假定教科書中的執行模型並不完全是Prolog所使用的?

是當然的,真正的Prologs有cutfail不像你提到的抽象解釋。該解釋沒有明確的回溯,而是用魔法有多個成功(其選擇本質上是不確定性彷彿所有的選擇都在一經作出,並行 - 真正Prologs只有模擬通過順序執行與明確的回溯,其中cut是指 - 它根本沒有其他含義)。

+1

謝謝你的回答。我以爲 !只意味着「承諾已經在當前替代的綁定,並不再退縮」......我不明白爲什麼解決方案會變空。 – josh 2012-07-24 10:54:18

+0

例如,此處的剪切不會使解析變空:a(X): - write('one'),!,write('two')。 ------ b(X): - a(X),寫('三')。這將實際上打印「onetwothree」(「三」被打印因爲「寫」('三')「沒有從解決方案中刪除時找到切割。我仍然有點困惑。 – josh 2012-07-24 11:03:08

+3

請注意那個解釋器是暗示的,當談到「選擇」時,它說「任何一個匹配的人都可以選擇」,但是接着談到「如果做出了另一個選擇會怎麼樣」,這意味着解釋者的一個「運行」解釋了一個結果。但這意味着所有可能的選擇都已經完成,所以***所有可能的結果***都已經到達了。「Cut」就是減少找到的結果的數量,它說:「不要搜索更多的結果,我現在所擁有的就足夠了「,你不會把它放到解決方案中,它不是一個邏輯目標,它是一個操作命令2intrptr。 – 2012-07-24 13:16:36

2

你有嵌套了一層額外的在您的測試目標:

n(n(f(X)) 

代替:

n(f(X)) 

事實上,如果我們嘗試,它按預期工作:

$ prolog 
GNU Prolog 1.3.0 
By Daniel Diaz 
Copyright (C) 1999-2007 Daniel Diaz 
| ?- [user]. 
compiling user for byte code... 
n(X) :- call(X), !, fail. 
n(_X). 
f(a). 

user compiled, 4 lines read - 484 bytes written, 30441 ms 

yes 
| ?- f(a). 

yes 
| ?- n(f(a)). 

no 
| ?- n(f(42)). 

yes 
| ?- n(n(f(X))). 

yes 
| ?- n(f(X)). 

no 
| ?- halt. 

所以你對Prolog的理解是正確的,你的測試用例不是!

更新

顯示否定的否定的效果:

$ prolog 
GNU Prolog 1.3.0 
By Daniel Diaz 
Copyright (C) 1999-2007 Daniel Diaz 
| ?- [user].                
compiling user for byte code... 
n(X) :- format("Resolving n/1 with ~q\n", [X]), call(X), !, fail. 
n(_X). 
f(a) :- format("Resolving f(a)\n", []). 

user compiled, 4 lines read - 2504 bytes written, 42137 ms 

(4 ms) yes 
| ?- n(f(a)). 
Resolving n/1 with f(a) 
Resolving f(a) 

no 
| ?- n(n(f(a))). 
Resolving n/1 with n(f(a)) 
Resolving n/1 with f(a) 
Resolving f(a) 

yes 
| ?- n(n(n(f(a)))). 
Resolving n/1 with n(n(f(a))) 
Resolving n/1 with n(f(a)) 
Resolving n/1 with f(a) 
Resolving f(a) 

no 
| ?- n(n(n(n(f(a))))). 
Resolving n/1 with n(n(n(f(a)))) 
Resolving n/1 with n(n(f(a))) 
Resolving n/1 with n(f(a)) 
Resolving n/1 with f(a) 
Resolving f(a) 

yes 
| ?- halt. 
+0

我明白,如果我遞歸調用目標一切正常,但書中呈現的執行模型不能遞歸工作。它是迭代的(並且不使用堆棧)。新的目標只是附加到解決方案,如果我遵循該模型,則查詢應該失敗(實際上不會)。 – josh 2012-07-23 14:16:58

+1

我不知道你是什麼意思關於遞歸與迭代。分辨率*是遞歸算法。你沒有得到你期望的答案的原因是因爲你每次添加一個額外的'n()'項否定否定。我已經更新了我的答案以顯示這種情況。 – 2012-07-23 14:46:25

+0

我的意思是「call(X)」是對Prolog引擎的遞歸調用。如果不是,那麼它只會將X追加到resolvent - 然後n(n(f(a)))將失敗。 – josh 2012-07-23 15:22:05

5

你的程序是不是一個純粹的Prolog程序,因爲它包含A/0 N/1!您可能會問簡單的問題:你的定義,爲什麼查詢?- n(f(X)).失敗雖然有清楚的是事實N(X)在你的程序,這意味着N(X)是 X真實的,應因此特別是f(X)也是如此?這是因爲由於使用!/ 0,程序的子句不再被孤立地考慮,而且純Prolog的執行模型也不能使用。對於這樣不純的謂詞,更現代和純粹的選擇通常是約束條件,例如dif/2,可以將變量約束爲與術語不同。

1

雖然mat是正確的,你的程序不是純序言(這與本章的標題是Pure Prolog相關),不僅因爲你使用了剪切,還因爲你編寫了處理其他謂詞的謂詞(純序言是一階邏輯的子集)這不是主要問題;你只是缺少回溯

雖然你確實有削減,直到目標n(f(X))成功,這將不會達成。但是,正如你所知道的那樣,這將會失敗,因此prolog會回溯並匹配第二個子句。

我不明白這與6.1中描述的模型會有什麼矛盾(並且會發現很難相信其他書會描述一個模型,在失敗之後執行會繼續,因此允許剪切修剪其他模型解決方案)。無論如何,我發現跳到「Prolog實現在教科書中不依賴於執行模式行爲」這一結論與「編譯器存在缺陷」非常相似,特別是因爲「反例」的行爲如同它應該(不(不(真))應該是真實的)

+1

我當然不是說實現不正確。其實我試圖理解書本爲什麼沒有更精確地描述執行模型。直覺地,我也知道n(f(a))將會失敗,Prolog會回溯。然而,這與執行模型不匹配,以「呼叫(X)」不是遞歸調用解釋器。那麼我認爲「call(X)」不是純粹的Prolog,這就是n(n(f(a)))成功的原因。 – josh 2012-07-23 15:20:16

6

下面的標題不告訴你這個特定的算法是關於什麼的:

圖4.2一個抽象的解釋邏輯程序

此外,它的說明上寫着:

輸出: G的一個實例,這是 的邏輯結果P,否則沒有。

也就是說,4.2中的算法僅向您顯示如何計算邏輯程序的邏輯結果。它只是讓你知道Prolog是如何實際工作的。特別是不能解釋!。此外,4.2中的算法只能解釋如何找到一種解決方案(「後果」),但Prolog試圖以系統化方式找到所有這些解決方案,稱爲按時間順序回溯。切割以非常特殊的方式干擾按時間順序回溯,這在該算法的層面無法解釋。

您寫道:

另外(同一本書的第120頁),Prolog的選擇目標 (choose goal A)在左到右的順序,並在他們的程序出現的順序搜索條款 (choose renamed clause ...)

未命中,你可以120頁上的閱讀很重要的一點:通過選擇最左邊的目標從抽象解釋得到

的Prolog的執行機制...並通過順序搜索一個可統一的子句 和回溯來替代子句的非確定性選擇。

所以正是這個小小的加法和「回溯」讓事情變得更加複雜。你不能在抽象算法中看到這一點。

下面是一個很小的例子,表明在算法中沒有明確處理回溯。

p :- 
q(X), 
r(X). 

q(1). 
q(2). 

r(2). 

我們將開始與p這是改寫到q(X), r(X)(有沒有其他辦法繼續)。

然後,選擇q(X),並且θ= {X = 1}。所以我們有​​作爲解決方案。但是現在,我們沒有任何匹配條款,所以我們「退出while循環」並回答

但是等等,有一個解決方案!那麼我們如何得到它?當選擇q(X)時,還有另一種θ選項,即θ= {0}。算法本身並不明確執行此操作的機制。它只是說:如果你在任何地方做出正確的選擇,你會找到答案。爲了從抽象的算法中獲得真正的算法,我們需要一些機制來實現這一點。

2

我想你說得對了。問題在這裏:

RESOLVENT: !, fail, !, fail. 

第一個!並且從第一個子句匹配的第二個時間失敗。另外兩個是從開始的時間。

RESOLVENT: ![2], fail[2], ![1], fail[1]. 

切割和失敗對正在處理的子句有影響 - 不在「調用」它的子句上。如果您再次執行這些步驟,但是使用這些註釋,您將獲得正確的結果。

![2], fail[2]使第二次調用n失敗,沒有回溯。但其他調用(第一批)仍然可以原路返回 - 這將:

RESOLVENT: n(_) 

,其結果是「是」。

這表明Prolog使用堆棧規則保留有關回溯的信息。您可能對用作Prolog實現模型的虛擬機感興趣。它比您提到的執行模型複雜得多,但將Prolog翻譯成虛擬機會讓您更準確地理解Prolog的工作原理。這是沃倫抽象機器(WAM)。 tutorial by Hasan Aït-Kaci是你會找到它的最好的解釋(它解釋了切割,如果我沒有記錯的話,在原始的WAM描述中沒有)。如果您不習慣抽象理論文本,您可以嘗試先閱讀Peter van Roy的文字:「1983-1993: the wonder years of sequential Prolog implementation」。這篇文章很明確,基本上講述了Prolog實現的歷史,但特別關注了WAM。但是,它沒有顯示如何實施剪輯。但是,如果您仔細閱讀,您可能會拿起Hasan的教程並閱讀他實施剪輯的部分。

+0

你的解釋很清楚,謝謝! – josh 2012-07-25 12:24:12