學習到這個階段,你可能已經使用append/3和member/2寫了不少程序。你可能每次都須要將它們的實現代碼拷貝到使用它們的程序文件中。並且,通過幾回這樣作以後,你就會感受每次不停的拷貝是很是重複和麻煩的事情。若是你能夠在一個文件中定義它們,而後在須要的地方使用,這將會是使人愉快的,並且也是更加合理的作法。固然,Prolog提供了這樣的方式去組織程序。程序員
事實上,你已經知道有一種方式,能夠告訴Prolog讀取文件中的謂詞定義,以下:app
[FileName1]
而且這個命令能夠告訴Prolog對文件中的內容進行編譯。可是有兩件有用的事情你也應該知道:第一,你能夠一次編譯多個文件:模塊化
[FileName1, FileName2, ..., FileNameN]
第二,更爲重要的是,文件編譯不只僅能夠用於交互環境,若是你將下面代碼:性能
:- [FileName1, FileName2, ..., FileNameN].
放在你的程序代碼文件頭部(好比命名爲main.pl的文件),那麼事實上你是告訴Prolog首先編譯列出的文件,而後再讀取程序的其餘部分。學習
這個特性給予了咱們重用謂詞的簡單方式。好比,假設你將全部基礎列表操做相關的謂詞放在一個文件中(好比append/3,member/2,reverse/2等等),名字是listPredicates.pl,若是你想要使用它們,能夠這麼輸入:設計
:- [listPredicates].
到須要使用它們的程序文件的開頭。Prolog將會編譯listPredicates當讀取程序文件時,因此listPredicates中定義的全部謂詞能可用了。code
這裏有一點須要注意的,當Prolog裝載讀取文件時,它不會檢查這些文件是否已經編譯過。若是文件中的謂詞已經存在在知識庫中,由於文件在以前已經編譯過,Prolog仍是會從新編譯它們,雖然這徹底沒有必要。並且在編譯很是大的文件時,會致使性能的損耗。開發
內置的謂詞 ensure_loaded/1 能夠更加智能的加載讀取文件,它的效果和以前的一致,使用以下:it
:- ensure_loaded([listPredicates]).
Prolog將會檢查listPredicates.pl是否已經加載過,而且當文件有修改時,纔會再次加載。編譯
如今設想你正在寫一個程序進行電影知識庫的管理。你已經設計了一個謂詞printActors用於打印一部電影的主要演員,以及一個謂詞printMovies用於打印某個導演的全部電影。兩個不一樣的定義儲存在不一樣的文件中,名爲printActors.pl和printMovies.pl,同時兩個文件都有名爲displayList/1的輔助謂詞。下面是第一個文件內容:
% This is the file: printActors.pl printActors(File) :- setof(Actor, starring(Actor, File), List), displayList(List). displayList([]) :- nl. displayList([X | L]) :- write(X), tab(1), displayList(L).
下面是第二個文件內容:
% This is the file: printMovies.pl printMovies(Director) :- setof(File, directed(Director, File), List), displayList(List). displayList([]) :- nl. displayList([X | L]) :- write(X), nl, displayList(L).
注意displayList/1在兩個文件中有不一樣的定義:打印演員是用按行的方式(使用tab/1),而打印電影是用按列的方式(使用nl/0)。這會讓Prolog困惑嗎?讓咱們看看,咱們使用下面的代碼將兩個文件同時加載:
% This is the file: main.pl :- [printActors]. :- [printMovies].
上面的代碼寫在main.pl文件的開頭,編譯主程序文件會報出以下的提示信息:
?- [main]. {consulting main.pl...} {consulting printActors.pl...} {printActors.pl consulted, 10 msec 296 bytes} {consulting printMovies.pl...} The procedure displayList/1 is being redefined. Old file: printActors.pl New file: printMovies.pl Do you really want to redefine it? (y, n, p, or ?)
發生了什麼?因爲printActors.pl和printMovies.pl兩個文件都定義了名爲displayList/1的謂詞,因此Prolog須要選擇其中的一個定義(同一謂詞在知識庫中不能有不一樣的定義)。
如何解決這個問題呢?在有些狀況下,你可能真的須要從新定義謂詞。可是如今你不能——由於打印演員和打印電影但願使用不一樣的方式進行。有一種解決方法是:給其中一個謂詞另一個名字。可是這個方法是很笨拙的。你但願每一個文件都是邏輯自包含的程序實體,不但願浪費時間和精力爲了其餘文件而進行謂詞的從新命名。因此獲取概念獨立性最天然的方式是使用Prolog的模塊系統。
模塊從根本上容許隱藏謂詞定義。你能夠決定哪些謂詞應該是對外公開的(即,能夠由其餘文件的程序來調用),以及哪些謂詞應該是私有的(即,只能在模塊內部調用)。這樣的話,就不容許在模塊外部調用私有的謂詞,這樣兩個模塊內部名字同樣的私有謂詞就不會發生衝突了。在咱們的例子中,displayList/1這個謂詞是私有謂詞的最好候選:它都是在各自的文件中起輔助做用的,而且兩個實現之間沒有任何關聯。
能夠將文件轉換爲模塊,只須要在文件開頭進行模塊聲明。模塊聲明的形式以下:
:- module(ModuleName, List_of_Predicates_to_be_Exported).
上面的聲明指定了模塊的名字,及其公共謂詞的列表,即你但願導出的謂詞列表。這些謂詞是可以在模塊外部被訪問的。
讓咱們將電影知識庫程序的文件模塊化。咱們只須要在第一個文件的開頭包含下面一行:
% This is ths file: printActors.pl :- module(printActors, [printActors/1]). printMovies(Director) :- setof(File, directed(Director, File), List), displayList(List). displayList([]) :- nl. displayList([X | L]) :- write(X), nl, displayList(L).
這裏咱們定義了一個稱爲printActors的模塊,有一個公共謂詞printActors/1。謂詞displayList/1僅僅在模塊printActors內部可見,因此它的定義不會對其餘模塊有任何影響。
相似地,咱們能夠將第二個文件也模塊化:
% This is the file: printMovies.pl :- module(printMovies, [printMovies/1]). printMovies(Director) :- setof(File, directed(Director, File), List), displayList(List). displayList([]) :- nl. displayList([X | L]) :- write(X), nl, displayList(L).
一樣地,displayList/1的定義只在printMovies這個模塊中可見,因此在加載兩個模塊文件中Prolog不會出現衝突和崩潰了。
可使用內置謂詞use_module/1加載模塊,模塊中全部公共謂詞都會被導入到當前知識庫中。換種說法,全部公共謂詞都是可以訪問的。修改main.pl文件:
:- use_module(printActors). :- use_module(printMovies).
若是你不但願使用模塊中的全部公開謂詞,而只是其中的一部分,你可使用兩個參數版本的use_module,其中第二個參數是你真正但願使用的公開謂詞列表,以下:
% This is the file: main.pl :- use_module(printActors, [printActors/1]). :- use_module(printMovies, [printMovies/1]).
在main文件的頭部,咱們顯式地指出須要使用的謂詞是:printActors/1和printMovies/1,並且不須要其餘的謂詞(固然在這個例子中,也沒有其餘公開的謂詞可使用了)。
不少通用的謂詞在大多數Prolog實現中,已經經過一種或者多種方式預先定義了。若是你在使用SWI Prolog,可能已經注意到諸如append/3和member/2這些謂詞已是系統的組成部分。這是SWI Prolog特有的。其餘一些Prolog的實現,好比SICStus,並無內置這些謂詞,都是經過庫的方式提供的。
庫是定義了通用謂詞的一些模塊,而且可以使用相同的命令進行加載使用。當你指定想要使用的特定庫名字時,你必需要告訴Prolog將模塊做爲庫的形式來加載,這樣Prolog才知道去什麼地方找到這些模塊(即,Prolog有特定的存放庫的路徑,並非程序代碼所在的路徑)。好比,在程序文件的頭部輸入下面的命令:
:- use_module(library(lists)).
將會告訴Prolog加載名字爲lists的庫。在SICStus Prolog中,這個庫包含了一系列通用的列表處理謂詞。
庫十分有用而且可以提升開發效率。並且,庫中的代碼都是優秀程序員寫的,幾乎都是執行效率很高,而且錯誤不多的。可是不一樣的Prolog實現中,庫的組織和實現的謂詞並無統一的標準。這意味着若是你想要你的程序運行在不一樣的Prolog實現下,定義本身的庫模塊是更加容易和效率的,這樣不用嘗試去理解和解決不一樣Prolog實現下的兼容性問題。