• Learn Prolog Now 翻译


    学习到这个阶段,你可能已经使用append/3和member/2写了很多程序。你可能每次都需要将它们的实现代码拷贝到使用它们的程序文件中。而且,经过几次这样做之后,你就会感觉每次不停的拷贝是非常重复和麻烦的事情。如果你可以在一个文件中定义它们,然后在需要的地方使用,这将会是令人愉快的,而且也是更加合理的做法。当然,Prolog提供了这样的方式去组织程序。


    程序中的读操作



    事实上,你已经知道有一种方式,可以告诉Prolog读取文件中的谓词定义,如下:

    [FileName1]
    

    并且这个命令可以告诉Prolog对文件中的内容进行编译。但是有两件有用的事情你也应该知道:第一,你可以一次编译多个文件:

    [FileName1, FileName2, ..., FileNameN]
    

    第二,更为重要的是,文件编译不仅仅可以用于交互环境,如果你将下面代码:

     :- [FileName1, FileName2, ..., FileNameN].
    

    放在你的程序代码文件头部(比如命名为main.pl的文件),那么事实上你是告诉Prolog首先编译列出的文件,然后再读取程序的其他部分。

    这个特性给予了我们重用谓词的简单方式。比如,假设你将所有基础列表操作相关的谓词放在一个文件中(比如append/3,member/2,reverse/2等等),名字是listPredicates.pl,如果你想要使用它们,可以这么输入:

    :- [listPredicates].
    

    到需要使用它们的程序文件的开头。Prolog将会编译listPredicates当读取程序文件时,所以listPredicates中定义的所有谓词能可用了。

    这里有一点需要注意的,当Prolog装载读取文件时,它不会检查这些文件是否已经编译过。如果文件中的谓词已经存在在知识库中,因为文件在之前已经编译过,Prolog还是会重新编译它们,虽然这完全没有必要。而且在编译非常大的文件时,会导致性能的损耗。

    内置的谓词 ensure_loaded/1 可以更加智能的加载读取文件,它的效果和之前的一致,使用如下:

    :- 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实现下的兼容性问题。

  • 相关阅读:
    多线程和异步
    Win7 系统证书链错误
    应用项目迁移到 dotnet 6 的经验和决策
    工具大全(自动化、接口、性能、安全、测试管理)
    从零搭建 Npm 包持续集成
    进程、线程和协程
    Drools 入门案例——手把手教你
    如何安装Virtual Box的VBox Guest Additions扩展程序
    betterZip解压后压缩包会删除 zip文件解压后原压缩文件能不能删掉
    undefined method `license' when mac brew install
  • 原文地址:https://www.cnblogs.com/seaman-h-zhang/p/4710709.html
Copyright © 2020-2023  润新知