学习到这个阶段,你可能已经使用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实现下的兼容性问题。