Prolog modules madness

Try the following experiment in a Prolog compiler supporting modules such as SWI-Prolog or YAP. Define two modules that export the same predicate and try to load them. For example, assume a file m1.pl with content:

:- module(m1, [p/1]).
 
p(X) :- clause(X, true).
 
:- dynamic(a/1).
a(one). a(two). a(three).

and a file m2.pl with content:

:- module(m2, [p/1]).
 
p(X) :- clause(X, true).
 
:- dynamic(a/1).
a(1). a(2). a(3).

Let’s try to load these two files in SWI-Prolog:

$ swipl
Welcome to SWI-Prolog (Multi-threaded, 32 bits, Version 5.7.7)
Copyright (c) 1990-2008 University of Amsterdam.
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
Please visit http://www.swi-prolog.org for details.
 
For help, use ?- help(Topic). or ?- apropos(Word).
 
?- [m1, m2].
% m1 compiled into m1 0.00 sec, 1,316 bytes
ERROR: Cannot import m2:p/1 into module user: already imported from m1
false.
 
[debug]  ?-

We get an error. It seems that loading a module automatically imports its exported predicates into the loading context (the module user in this case). How unfortunate. We don’t get a chance of using explicit qualified calls to choose which predicate to call. Let’s try the same experiment with YAP:

$ yap
% Restoring file /usr/local/lib/Yap/startup
YAP version Yap-5.1.4
   ?- [m1, m2].
 % consulting /Users/pmoura/m1.pl...
 % consulted /Users/pmoura/m1.pl in module m1, 1 msec 1056 bytes
 % consulting /Users/pmoura/m2.pl...
 % consulted /Users/pmoura/m2.pl in module m2, 0 msec 648 bytes
NAME CLASH: m1:p/1 was already imported to module user;
            Do you want to import it from m2 ? [y or n]

In this case, we get a prompt asking what to do. Maybe interesting if we happen to be in an interactive session, otherwise…

Ciao have claimed for a long time to provide a better module system so let’s try a similar experiment with this compiler. This time we explicitly say that we want to use both modules:

$ ciao
Welcome to the Ciao Prolog Development System!
 
Ciao-Prolog 1.10 #8: Wed Jan 31 21:54:06 WET 2007
?- use_module(m1), use_module(m2).
 
yes
?- p(a(X)).
 
X = one ? ;
X = two ? ;
X = three ? ;
no

In this case, there is no error, no prompt, the compiler just silently makes a choice in our behalf. How can the compiler know how to make the right choice? Should the order of the use_module/1 calls really matter? What if there are two exported predicates with the same name in both modules and we want one from the first module and the other from the second module?

What these experiments tell us?

Prolog compilers automatically import, or try to import, the exported predicates of loaded modules. Therefore, whenever two modules export the same predicate, we either get an error (SWI-Prolog), a prompt for solving the conflict (YAP), or automatic conflict resolution (Ciao). So much for the often proclaimed de facto module standard.

Modules are supposed to provide namespaces. Except, it seems, for their exported predicates. In this case, a module developer needs to be aware of all the exported predicates in all modules that might be used by him, or by her, or by third parties in order not to get into trouble. Or risk the chance of getting his or her users into trouble. This is the amazing foundation that many implementers and users alike feel that should be used to build the predicate libraries that will support our Prolog applications. My biggest problem is, of course, how to hide this post from colleagues working in other programming languages. I don’t want them to laugh themselves to death.


9 Responses to “Prolog modules madness”

  • Parker

    Another interesting post, Paulo, and I have run into this very problem myself.

    I see this problem with modules as representative of a more general issue with Prolog. Effort in improving Prolog seems to be concentrated on “programming in the small”. Prolog is a very interesting language because it is amenable to all kinds of analysis and optimization. There is much scope for improvement — and that is great. But I have seen few efforts concerning Prolog “programming in the large”. For some reason this topic is hardly mentioned. Hundreds of posts discuss implementation of language primitives, but software engineering principles and Prolog’s support for them are largely ignored.

    Prolog has dug itself into a hole: sadly it is now only well-suited to academic exercises. The time is ripe for some initiatives to address logic programming in the large.

  • Chris Mungall

    I don’t think it’s as bad as you make out.

    Why not just use use_module/2 with an empty list as 2nd argument and explicitly prefix the predicate with the module name whenever there is a clash? This seems no worse than in say, java, where there are frequently class name clashes.

    I agree with Parker’s sentiment, re: programming in the large.

    Although javascript gets by without a module system, and is used for some large applications.

    • Paulo Moura

      Actually, is worse. Take a look at the predicate names in most Prolog libraries. Ask yourself why e.g most of the predicates in YAP rbtrees module have a rb_ prefix or why most of the predicates in SWI-Prolog ordsets have a ord_ prefix. Writing ordesets::ord_symdiff(..., ...) sounds, of course, a bit silly but that’s what you get when using explicit qualification for calling module predicates. The prefixes exist because the recommended way of using module predicates is using use_module/1 and implicit qualification. Thus, in order to avoid name clashes, you end up naming predicates with a prefix based on the name of the module encapsulating them! The proper place to solve name conflicts is in the import module, not in the imported modules. Some well know Prolog developers and implementers goes as far as recommending that prefixes shall be avoided or dropped altogether. Also, keep in mind that, until recently, using :/2 for calling module predicate would result in a performance penalty in several Prolog compilers.

    • Paulo Moura

      Btw, explicit qualification of module meta-predicates requires the explicit qualification of the meta-arguments. Hardly elegant.

  • Joachim Schimpf

    I’m glad to say that ever since a major overhaul of its module system in 2000, ECLiPSe (eclipse-clp.org) gets these things right:

    ?- use_module(m1), use_module(m2).
    m1.ecl compiled 144 bytes in 0.01 seconds
    m2.ecl compiled 144 bytes in 0.00 seconds
    Yes (0.12s cpu)

    ?- p(a(X)).
    Ambiguous import of p / 1 from [m1, m2] in module eclipse
    calling an undefined procedure p(a(X)) in module eclipse
    Abort

    ?- m1:p(a(X)).
    X = one
    Yes (0.00s cpu, solution 1, maybe more) ? ;
    X = two
    Yes (0.00s cpu, solution 2, maybe more) ? ;
    X = three
    Yes (0.00s cpu, solution 3)

    ?- m2:p(a(X)).
    X = 1
    Yes (0.00s cpu, solution 1, maybe more) ? ;
    X = 2
    Yes (0.00s cpu, solution 2, maybe more) ? ;
    X = 3
    Yes (0.00s cpu, solution 3)

    > Btw, explicit qualification of module meta-predicates requires the > explicit qualification of the meta-arguments. Hardly elegant.

    True for the de-facto standard. Not the case in ECLiPSe, where colon-qualification only changes predicate lookup, not the calling context.

  • Jan Burse

    From the SICTUS 7 manual:
    Clashes with already existing predicates, local or imported from other modules, are handled in two different ways: If the receiving module is the user module, the user is asked for redefinition of the predicate. For other receiving modules, a warning is issued and the importation is canceled. In the first case redefinition silently takes place if the flag redefine_warnings has the value off (see prolog_flag/3). The binding of an imported predicate remains, even if the origin is reloaded or deleted. However, abolish/1 breaks up the importation binding. When a module file is reloaded, a check is made that the predicates imported by other modules are still in the public list. If that is not the case, a warning is issued. Note that an imported predicate may be re-exported.
    Some predicates take goals as arguments (i.e. meta-predicates). These arguments must include a module specification stating which module the goal refers.

    • Paulo Moura

      Note that most (if not all) Prolog implementers are aware of the module issues discussed in my posts. The major roadblock in solving these issues is backward compatibility, which Prolog implementers are afraid to break and/or don’t want to break. ECLiPSe is a notable exception in this panorama, as Joachim pointed out on his comments.

  • Stewart Sims

    I’m not convinced introducing extra programming paradigms to a prolog implementation is the way forward in terms of modularising code. I actually think the module system of SWI-Prolog is very practically useful. My opinions probably bias what I say, but I honestly think Prolog is a useful language in itself, and writing large functional software with it is a total reality. I think the issue here has much more to do with a developer’s approach in terms of their overall architecture. It is possible to make Prolog talk to an OO based language in a way that is mutually beneficial and does not confuse the two, but I think this method of interaction should be the programmer’s decision and not made for them by the design of the language itself (as is with multi-paradigm languages).

    • Paulo Moura

      Hi Stewart,

      Thanks for your comment. If you’re satisfied by the SWI-Prolog module system then, by all means, use it. Some of us need more than a Prolog module system can offer. But note that the post your are commenting is about portability across module systems. Regarding making Prolog talk with an OO language, what does that have to do with Logtalk? It seems that you’re thinking of imperative-derived OO languages. Together with your remark about programmer’s decisions, is clear that you you’re misinterpreting what Logtalk is all about. May I suggest that you take a look at this presentation:

      From Plain Prolog to Logtalk Objects: Effective Code Encapsulation and Reuse (Invited Talk)

      Best regards,

      Paulo