避免 Prolog 的 'Horrors':宣告純淨指南

Prolog 是一種語言,常吸引具有叛逆傾向的程式設計師——那些尋求超越主流產業標準範式來解決問題的人。然而,這種叛逆如果誤將語言的彈性視為可以忽視其邏輯基礎的許可證,可能會將開發者引向「編碼恐怖」的道路。

編寫出色的 Prolog 程式不需要複雜的精通;相反地,它需要遵守一小套規則,以保持語言的宣告特性。當這些規則被破壞時,程式會變得有缺陷——不一定是因為崩潰,而是因為未能真正具備關係性。

Prolog 缺陷的本質

即使一個 Prolog 程式能夠終止並高效運行,它仍可能在兩種根本方式上出現錯誤:

  1. 報告錯誤答案:程式提供不正確的結果。
  2. 未能報告預期的解答:程式遺漏了有效的解答。

雖然第一種顯然有問題,但第二種往往更具隱蔽性。如果程式報告錯誤答案,你可能可以將它們過濾掉。但如果程式未能報告在邏輯上存在的解答,通常無法在不重寫邏輯的情況下恢復該遺失的資訊。

常見的 Prolog 反模式

1. 透過不純度遺失解答

許多開發者依賴不純且非單調的結構來控制執行。最常見的罪魁禍首是「切割」運算子 (!/0),以及 (->)/2var/1。雖然這些可以優化效能或防止不必要的回溯,但它們常常破壞程式尋找所有有效解答的能力,將關係轉變為單向函式。

宣告性替代方案: 使用乾淨的資料結構、約束(例如 dif/2)以及類似 if_/3 的 meta-predicate,在不犧牲純度的情況下處理條件邏輯。

2. 全域狀態的陷阱

初學者常被誘惑透過 assertz/1retract/1 使用全域資料庫。這會引入隱式依賴;程式的正確性變得依賴於執行順序,而非事實之間的邏輯關係。

宣告性替代方案: 透過述語引數明確傳遞狀態,或使用 semicontext 符號將狀態「穿線」穿過計算。

3. 不純輸出與副作用

在述語內使用 format/2 直接將答案印到終端機是一個常見錯誤。這會阻止程式碼被當作真正的關係使用,並且因為輸出僅存在於螢幕上而非 Prolog 項目,使得自動化測試變得幾乎不可能。

宣告性替代方案: 在程式碼中描述解答,並讓最上層處理印刷。如果需要特殊格式,請使用純粹的方式描述輸出(例如 format_//2),以使輸出保持為可測試的項目。

4. 依賴低層級結構

許多程式設計師堅持使用過時的低層級算術述語,如 is/2=:=/2>/2。這些要求程式設計師同時管理宣告語意和操作語意,使得程式碼難以閱讀與維護。

宣告性替代方案: 使用有限域上的約束邏輯程式設計(CLP(FD))。透過教授和使用約束,語言變得更直觀,程式也更具普遍性。

案例研究:恐怖的階乘

考慮一個典型的「恐怖」階乘實現:

horror_factorial(0, 1) :- !.
horror_factorial(N, F) :- 
    N > 0, 
    N1 is N - 1, 
    horror_factorial(N1, F1), 
    F is N*F1.

此版本有缺陷。如果您詢問最一般的查詢 — — ?- horror_factorial(N, F). — — 它只會返回 N = 0, F = 1,然後因為切割 (!) 而停止。如果您移除切割,您會遇到「實例化錯誤」,因為 is/2 要求其參數必須被實例化。

透過轉向純粹、基於約束的方法,我們得到一個真正具關係性的程式:

n_factorial(0, 1).
n_factorial(N, F) :- 
    N #> 0, 
    N1 #= N - 1, 
    n_factorial(N1, F1), 
    F #= N*F1.

現在,最一般的查詢 ?- n_factorial(N, F). 將正確生成所有階乘對 (N=0, F=1; N=1, F=1; N=2, F=2...),無限期地。

辯論:純度 vs. 實用性

雖然追求純度的動力很強大,但它並非沒有批評者。一些有經驗的從業者認為「恐怖」標籤被誇大了。正如社區討論所指出:

"我會把他的清單歸類為『需要小心處理的事情』,而不是『編碼恐怖』……我同意在第二階段變更資料庫可能是個壞主意,但這不等於說 assertz() 一直不好。"

其他人指出,該語言提供了不純結構,專門用於處理邊緣情況,否則將需要複雜的函式庫匯入。正如 Prolog Coding Guidelines 所建議的,關鍵在於謹慎且精準地使用切割,並且除非你真的需要透過回溯保存資訊,否則應避免使用 assert/retract

結論

程式設計中的叛逆在帶來更好抽象時是有價值的,但當它堅持過時的模式時,則會適得其反。透過擁抱 Prolog 的純粹單調子集並利用現代約束,開發者可以創建不僅更高效,而且在根本上更具普遍性且邏輯健全的程式。

Sources