12.1 Splitting Programs over Files

By this stage you have written lots of programs that use the predicates append/3 and member/2 . What you probably did each time you needed one of them was to go back to the definition and copy it over to the file where you wanted to use it. And maybe, after having done that a few times, you started thinking that it was quite annoying having to copy the same predicate definitions over and over again — how pleasant it would be if you could define them somewhere once and for all and then simply access them whenever you needed them. Well, that sounds like a pretty sensible thing to ask for and, of course, Prolog offers you ways of doing it.

Reading in programs

In fact, you already know a way of telling Prolog to read in predicate definitions that are stored in a file, namely the

   [FileName1]

command. You have been using queries of this form all along to tell Prolog to consult files. But there are two more useful things you should know about it. First, you can consult many files at once by saying

   [FileName1,FileName2,...,FileNameN]

instead. Second, and more importantly, file consultation does not have to be performed interactively. If you put

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

at the top of your program file (say main.pl ) you are telling Prolog to first consult the listed files before going on to read in the rest of your program.

This feature gives us a simple way of re-using definitions. For example, suppose that you keep all the predicate definitions for basic list processing (such as append/3 , member/2 , reverse/2 , and so on) in a file called listPredicates.pl . If you want to use them, simply put

   :-  [listPredicates].

at the top of the file containing the program that needs them. Prolog will consult listPredicates when reading in that file, and all the predicate definitions in listPredicates become available.

There’s one practical point you should be aware of. When Prolog loads files, it doesn’t normally check whether the files really need to be consulted. If the predicate definitions provided by one of the files are already in the database because that file was consulted previously, Prolog will still consult it again, although it doesn’t need to. This can be annoying if you are consulting very large files.

The built-in predicate ensure_loaded/1 behaves more intelligently in this respect. It works as follows. On encountering the following directive

   :-  ensure_loaded([listPredicates]).

Prolog checks whether the file listPredicates.pl has already been loaded and only loads it again if it has changed since the last loading.

Modules

Now imagine that you are writing a program that manages a movie database. You have designed a predicate printActors which displays all actors starring in a particular film, and a predicate printMovies which displays all movies directed by a particular filmmaker. Both definitions are stored in different files, namely printActors.pl and printMovies.pl , and both use an auxiliary predicate displayList/1 . Here’s the first file:

   %  This  is  the  file:  printActors.pl
   
   printActors(Film):-
         setof(Actor,starring(Actor,Film),List),
         displayList(List).
   
   displayList([]):-  nl.
   displayList([X|L]):-
         write(X),  tab(1),
         displayList(L).

And here’s the second:

   %  This  is  the  file:  printMovies.pl
   
   printMovies(Director):-
         setof(Film,directed(Director,Film),List),
         displayList(List).
   
   displayList([]):-  nl.
   displayList([X|L]):-
         write(X),  nl,
         displayList(L).

Note that displayList/1 has different definitions in the two files: the actors are printed in a row (using tab/1 ) , and the films are printed in a column (using nl/0 ) . Will this lead to conflicts in Prolog? Let’s see. We’ll load both programs by placing the statements

   %  This  is  the  file:  main.pl
   
   :-  [printActors].
   :-  [printMovies].

at the top of the main file. Consulting the main file will evoke a message that looks something like the following:

   ?-  [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  ?)

What has happened? Well, as both files printActors.pl and printMovies.pl define a predicate called displayList/1 , Prolog needs to choose one of the two definitions (it can’t have two different definitions for one predicate in its knowledge base).

What to do? Well, perhaps in some of these situations you really do want to redefine a predicate. But here you don’t — you want two different definitions because you want movies and actors to be displayed differently. One way of dealing with this is to give a different name to one of the two predicates. But let’s face it, this is clumsy. You want to think of each file as a conceptually self-contained entity; you don’t want to waste time and energy thinking about how you named predicates in some other file. And the most natural way of achieving the desired conceptual independence is to use Prolog’s module system.

Modules essentially allow you to hide predicate definitions. You are allowed to decide which predicates should be public (that is, callable from parts of the program that are stored in other files) and which predicates should be private (that is, callable only from within the module itself). Thus you will not be able to call private predicates from outside the module in which they are defined, but there will be no conflicts if two modules internally define the same predicate. In our example, displayList/1 is a good candidate for becoming a private predicate; it plays a simple auxiliary role in both printActors/1 and printMovies/1, and the details of the role it plays for one predicate are not relevant to the other.

You can turn a file into a module by putting a module declaration at the top. Module declarations are of the form

   :-  module(ModuleName,
                       List_of_Predicates_to_be_Exported).

Such declarations specify the name of the module and the list of public predicates, that is, the list of predicates that you want to export. These will be the only predicates that are accessible from outside the module.

Let’s modularise our movie database programs. We only need to include the following line at the top of the first file:

   %  This  is  the  file:  printActors.pl
   
   :-  module(printActors,[printActors/1]).
   
   printActors(Film):-
         setof(Actor,starring(Actor,Film),List),
         displayList(List).
   
   displayList([]):-  nl.
   displayList([X|L]):-
         write(X),  tab(1),
         displayList(L).

Here we have introduced a module called printActors , with one public predicate printActors/1 . The predicate displayList/1 is only known in the scope of the module printActors , so its definition won’t affect any other modules.

Likewise we can turn the second file into a module:

   %  This  is  the  file:  printMovies.pl
   
   :-  module(printMovies,[printMovies/1]).
   
   printMovies(Director):-
         setof(Film,directed(Director,Film),List),
         displayList(List).
   
   displayList([]):-  nl.
   displayList([X|L]):-
         write(X),  nl,
         displayList(L).

Again, the definition of the displayList/1 is only known in the scope of the module printMovies , so there won’t be any clash when loading both modules at the same time.

Modules can be loaded with the built-in predicates use_module/1 . This will import all predicates that were defined as public by the module. In other words, all public predicates will be accessible. To do this we need to change the main file as follows:

   %  This  is  the  file:  main.pl
   
   :-  use_module(printActors).
   :-  use_module(printMovies).

If you don’t want to use all public predicates of a module, but only some of them, you can use the two-place version of use_module , which takes a list of predicates that you actually want to import as its second argument. So, by putting

   %  This  is  the  file:  main.pl
   
   :-  use_module(printActors,[printActors/1]).
   :-  use_module(printMovies,[printMovies/1]).

at the top of the main file, we have explicitly stated that we can use printActors/1 and printMovies/1 , and nothing else (in this case, of course, the declaration is unnecessary as there are no other public predicates that we could use). Needless to say, you can only import predicates that are actually exported by the relevant module.

Libraries

Many of the most common predicates are provided predefined, in one way or another, by most Prolog implementations. If you have been using SWI Prolog, for example, you will probably have noticed that predicates like append/3 and member/2 come as part of the system. That’s a speciality of SWI, however. Other Prolog implementations, like SICStus for example, don’t have them built-in, but provide them as part of a library.

Libraries are modules defining common predicates, and can be loaded using the normal commands for importing modules. When specifying the name of the library that you want to use, you have to tell Prolog that this module is a library, so that Prolog knows where to look for it (namely, in the place where Prolog keeps its libraries, not in the directory where your other code is). For example, putting the directive

   :-  use_module(library(lists)).

at the top of your file tells Prolog to load a library called lists . In SICStus Prolog, this library contains a set of commonly used list processing predicates.

Libraries can be very useful and they can save you a lot of work. Moreover, the code in libraries has typically been written by excellent programmers, and is likely to be highly efficient and problem-free. However the way that libraries are organised and the inventory of predicates provided by libraries are by no means standardised across different Prolog implementations. This means that if you want your program to run with different Prolog implementations, it is probably easier and faster to define your own library modules (using the techniques that we saw in the last section) rather than to try to work around the incompatibilities between the library systems of different Prolog implementations.

eXTReMe Tracker
© 2006-2012 Patrick Blackburn, Johan Bos, Kristina Striegnitz