在進入正題之前,我們先一起思考一個問題:當我們在程式設計時,我們在做什麼?
看到這個問題,有些同學可能會第一時間要回答:
- 在聽音樂
- 在打字
- 在發呆
- 在裝作很忙的樣子
- ……
呃,我不是那個意思,說認真的:當我們在程式設計時,我們在做什麼?
我們在把所想的「解決方案」轉換成計算機可理解的「計算機語言」。
JavaScript, Python, Java, Ruby, C++ …… 等等等等,哪怕是世界上最好的語言 PHP,也大抵如此:「解決方案」->「計算機語言」。
但你有沒有想過反過來?「計算機語言」->「解決方案」 這樣,我們使用「計算機語言」描述問題,而計算機來告訴我們「解決方案」?
或許你會想到人工智慧、深度學習等等概念,但那些熱門話題不是我們今天要討論的。
今天,我們來一起聊聊那迷人的被遺忘的語言:Prolog.
Prolog 簡介
Prolog(Programming in Logic)是一種邏輯程式語言。它建立在邏輯學的理論基礎之上, 誕生與 1972 年,最初被運用於自然語言等研究領域,距今 46 年曆史。當下火熱的 JavaScript 語言 23 歲,年齡剛好是 Prolog 的一半。
說到 Prolog 是一種邏輯程式語言,那麼跟一般的函式式語言有什麼區別呢?答:完全不是一碼事情。在 Prolog 裡,最基本的做法是先描述事實(定立物件與物件之間的關係),然後用詢問目標的方式來查詢各種物件之間的關係,系統會自動進行匹配、回溯,並給出答案。
舉個例子:
我們使用下面的語法來表述一個事實
handsome(ergou).
複製程式碼
這個語句描述了一個事實:二狗 (ergou) 是無比帥氣 (handsome) 的,然後以句號 (.) 結束,表明這個事實描述完畢,不容置疑。
然後接下來我們在 Prolog 的控制檯 (REPL) 裡就會得到如下的結論:
?- handsome(X).
X = ergou.
?-
複製程式碼
其中 ?-
開頭的是我們手動輸入的,其他的是 Prolog 返回的。上面的那些操作是:
- 我們首先問 Prolog:誰是世界上最帥的人?
handsome(X).
其中X
是大寫字母開頭,在 Prolog 裡所有大寫字母開頭的都是變數。 - 然後 Prolog 回答說:
X = ergou.
,當然是二狗了!
以上,應該可以算作 Prolog 基本思路的一個演示。你可以去下載適用於你的 Prolog 環境,然後在自己的電腦上把玩一下。
建議使用 SWI Prolog(本文的所有演示程式碼都是在 SWI Prolog 進行的操作):www.swi-prolog.org/
安裝完畢之後,執行下面三步,即可復現上面的結果:
- 將事實寫入檔案
echo 'handsome(ergou).' > fact.prolog
複製程式碼
- 啟動 Prolog REPL 並載入既定事實:
swipl fact.prolog
複製程式碼
- 在 Prolog REPL 內輸入以下語句並回車:
handsome(X).
複製程式碼
誰是我大爺?
剛才的例子是我們描述了一個事實,然後又讓 Prolog 告訴了我們這個事實,好像沒有太大的用途,接下來我們試著做一下別的事情:
檔名:grandfather.prolog
father(yigou,ergou).
father(linggou,yigou).
grandfather(X,Z) :- father(X,Y), father(Y,Z).
複製程式碼
看了上面的程式碼,應該可以理解到,我們是描述了三個事實:
- 二狗 (ergou) 的父親是 一狗 (yigou)
- 一狗 (yigou) 的父親是零狗 (linggou)
- X 是 Z 大爺的前提條件是:X 是 Y 的父親,Y 是 Z 的父親
有了這些事實,我們可以幹什麼?可以找到二狗爺爺啊!
載入既定事實
swipl grandfather.prolog
複製程式碼
執行詢問語句
?- grandfather(X,yigou).
false.
?- grandfather(X,ergou).
X = linggou.
?-
複製程式碼
可以看到一狗 (yigou) 是沒有爺爺的,二狗 (ergou) 的爺爺是零狗 (linggou) 。
我和小紅的共同愛好是什麼?
像剛才那個例子,我們再舉一個來加深一下這種思維方式:
檔名:music.prolog
listen(ergou, bach). % 二狗 聽 巴赫
listen(ergou, beethoven). % 二狗 聽 貝多芬
listen(ergou, mozart). % 二狗 聽 莫扎特
listen(xiaohong, mj). % 小紅 聽 邁克爾·傑克遜
listen(xiaohong, dylan). % 小紅 聽 鮑勃·迪倫
listen(xiaohong, bach). % 小紅 聽 巴赫
listen(xiaohong, beethoven). % 小紅 聽 貝多芬
複製程式碼
上面這些程式碼描述了二狗和小紅的聽歌品味,現在如果二狗想要找出自己與小紅在聽歌品味上的共同之處,該怎麼辦?
載入既定事實
swipl music.prolog
複製程式碼
執行詢問語句
?- listen(ergou, X),listen(xiaohong,X).
X = bach ;
X = beethoven ;
false.
?-
複製程式碼
我們在載入了既定事實之後,開始詢問:有什麼音樂是二狗 (ergou) 和小紅 (xiaohong) 都有在聽的呢?
?- listen(ergou, X),listen(xiaohong,X).
複製程式碼
然後我們得到了下面的答案:
?- listen(ergou, X),listen(xiaohong,X).
X = bach
複製程式碼
如果你有在跟著文章手動操作,如果你是個細心的同學,你會發現這次詢問之後游標停留在了 X = bach
這裡,而不是換行顯示出 ?-
。這是為什麼呢?
這時候 Prolog 是在說:問題還有別的解答,按下 ;
尋求更多,按下 Enter
表示接受當前答案。在上邊了例子中,我們持續按下 ;
,直到返回 false
才停下來。
於是我們得到了二狗和小紅共同欣賞的音樂,是巴赫和貝多芬。
雞兔同籠問題
接下來我們來一個經典問題:
今有雉兔同籠,上有三十五頭,下有九十四足,問雉兔各幾何?
—— 《孫子算經》
翻譯成白話文:
有若干只雞兔同在一個籠子裡,從上面數,有35個頭,從下面數,有94只腳。問籠中各有多少隻雞和兔?
停!快停止思考!
如果你在使用 JavaScript、Python 等等語言的思路去嘗試程式設計解決問題的話,趕緊停下來,現在我們要用描述既定事實的方式,讓 Prolog 給我們答案!
檔名:chicken-and-rabbits.prolog
% 首先,我們引入一個 clpq 的庫來幫助我們進行運算子描述
:- use_module(library(clpq)).
% 然後,我們定義事實:腦袋的總數量(H) 應當等於 雞的總數量(C) 加上 兔子的總數量(R)
head(C,R,H) :- {H = C + R}.
% 然後,我們定義事實:腳的總數量(F) 應當等於 雞的總數量(C)乘以二 加上 兔子的總數量(R)乘以四
foot(C,R,F) :- {F = C*2 + R*4}.
複製程式碼
我們定義好了事實,那麼接下來讓我們去詢問 Prolog 答案:
載入既定事實
swipl chicken-and-rabbits.prolog
複製程式碼
執行詢問語句
?- head(C,R,35),foot(C,R,94).
C = 23,
R = 12.
複製程式碼
答:雞有 23 只!兔有 12 只!
哈哈哈哈,沒錯!再也不需要去絞盡腦汁思考怎麼告訴計算機去計算啦!只需要告訴 Prolog 既定事實,剩下的,就是提出正確的問題啦!
古老的 Prolog 在電腦科學中的逐漸退熱,必然有對應的原因,但這種語言所特有的思維方式,也不妨去探尋一下。在當下的程式設計氛圍裡,或許 Prolog 就像是夏日的一杯加冰檸檬水,給我們帶來涼爽和清新呢!
好了,關於 Prolog 的討論,我們今天就到這裡了,多謝各位!
還有更多關於 Prolog 的資料,感興趣可以繼續閱讀:
- Prolog - Wikipedia
- SWI-Prolog
- SWI-Prolog manual / clpqr
- Introduction to the Programming Language PROLOG: A Language for Logic Programming and Symbolic Computation
文 / 王二狗 職業程式設計師,週末愛好者
編 / 熒聲
本文已由作者授權釋出,版權屬於創宇前端。歡迎註明出處轉載本文。本文連結:knownsec-fed.com/2018-08-09-…
想要看到更多來自知道創宇開發一線的分享,請搜尋關注我們的微信公眾號:創宇前端(KnownsecFED)。歡迎留言討論,我們會盡可能回覆。
感謝您的閱讀。