Tag Archives: portability

Spring cleaning coming

The development of the current generation of Logtalk, 2.x, began on January of 1998. At that time, the ISO Prolog Standard (Part 1: General Core) was only three years old. Thus, accepting and dealing with the lack of compliance of most Prolog compilers with the standard was a sensible choice.

Back to the present. The ISO Prolog Standard is now 15 years old. Compliance with the standard greatly improved for some but not all Prolog compilers. Trying to keep minimal Logtalk compatibility with some of the existing Prolog compilers is no longer feasible. In fact, keeping compatibility with the most problematic Prolog compilers (as far as standard-compliance goes) is working as an anchor, slowing Logtalk development and preventing improvements and implementation of new features.

Some of the most problematic Prolog compilers (again, from the point-of-view of standards compliance) are (to the best of my knowledge) no longer being developed. Other Prolog compilers, actively maintained today, decided to ignore the current official and de facto standards. A few, such as IF/Prolog, provided good standard compliance but have been apparently discontinued by their developers.

I plan to ditch Logtalk compatibility with the following Prolog compilers in the upcoming 2.39.0 release: ALS Prolog, Amzi! Prolog, BinProlog, GNU Prolog, IF/Prolog, JIProlog, K-Prolog, LPA MacProlog, LPA WinProlog, Open Prolog, MasterProlog, PrologII+, Quintus Prolog. This may seem like a long list but I suspect this decision will have no consequence for most (if not all) Logtalk users. If If you think it is still worth to support some compiler in this list please contact me as soon as possible.

UPDATE: added GNU Prolog to the list of no longer supported compilers. Support for this compiler will be restored as soon as it implements the ISO Prolog standard directive multifile/1.


Using Logtalk to run Prolog module code in compilers without a module system

Logtalk can compile most Prolog modules as objects. This is accomplished by recognizing and parsing a common subset of Prolog module directives. For simple module code, it suffices to change the file name extensions from .pl to .lgt and compile the files as usual using the logtalk_load/1-2 built-in predicates. For modules that use proprietary predicates, directives, and syntax, some changes to the original code may be necessary.

As an example, assume that we want to use the SWI-Prolog modules lists, pairs, oset, and ordsets in GNU Prolog, a compiler that doesn’t support a module system. After making a copy of the original files and renaming their extensions to .lgt, we will need to remove a few specific, proprietary SWI-Prolog bits. First, we need to comment out all occurrences of the directive:

:- set_prolog_flag(generate_debug_info, false).

Second, we need to comment out in lists.lgt the calls to the must_be/2 predicate and the line:

:- use_module(error, [must_be/2]).

Still in lists.lgt we will need to replace the call to the succ/2 by:

M is N + 1,

Third, Logtalk doesn’t support the use_module/1 directive, requiring instead the use of the use_module/2 directive. Thus, we need to replace the directive:

:- use_module(library(oset)).

with the directive:

:- use_module(oset,
        [oset_int/3, oset_addel/3, oset_delel/3,
         oset_diff/3, oset_union/3]).

Finally, is_list/1 is a built-in predicate in SWI-Prolog but not in GNU Prolog. Logtalk provides its own portable versions of the is_list/1, succ/2, and must_be/2 predicates but let’s leave them out for now, for the sake of simplicity. After saving our changes, we are ready for a quick experiment:

$ gplgt
...
Logtalk 2.36.0
Copyright (c) 1998-2009 Paulo Moura
...
GNU Prolog 1.3.1
By Daniel Diaz
Copyright (C) 1999-2009 Daniel Diaz
| ?- {lists, pairs, oset, ordsets}.
...
yes
| ?- pairs::map_list_to_pairs(lists::length,[[1,2],[a,b,c],[X]],Pairs).
 
Pairs = [2-[1,2],3-[a,b,c],1-[X]]
yes

In SWI-Prolog the equivalent call would be:

$ swipl
...
Welcome to SWI-Prolog (Multi-threaded, 32 bits, Version 5.7.8)
...
?- pairs:map_list_to_pairs(length,[[1,2],[a,b,c],[X]],Pairs).
Pairs = [2-[1, 2], 3-[a, b, c], 1-[X]].

So, all done? Not quite. Different Prolog compilers provide different sets of built-in predicates. Module libraries use those built-in predicates. Thus, porting module code must also check for used built-in predicates. Fortunately, that’s quite easy in Logtalk: we simply compile the files with the portability compiler flag set to warning. In the case of our example, there is no problem for GNU Prolog but if we try to load our files in e.g. B-Prolog (another compiler without a module system) we will find that the lists module calls a memberchk/2 predicate that is not built-in in this compiler.

The porting process is not as straightforward as we may hoped for, but nothing is really straightforward when dealing with Prolog portability issues. The code changes needed are usually simple to apply as the example above illustrates. A small price to pay when porting module code. I’m sure you appreciate the irony of using Logtalk to run Prolog module code in module-less Prolog compilers. The subset of module directives recognized by Logtalk is enough for dealing with most Prolog module libraries. This subset could also provide a basis for a Prolog module standard based on current practice but that’s a topic for another post.

P.S. The predicate succ/2 is defined in the Logtalk library object integer. An equivalent predicate to is_list/1 is defined in the Logtalk library object list, which also defines a memberchk/2 predicate. Moreover, each Logtalk library object that defines a type contains a definition for a check/1 predicate that plays the same role as the SWI-Prolog library predicate must_be/2.


Logtalk 2.36.0 released

I released Logtalk 2.36.0 yesterday. The biggest news is support for settings files. At startup, Logtalk looks for and loads a settings.lgt file found in the startup directory. If not found, Logtalk looks for a loads a settings.lgt file in the Logtalk user folder. These settings files allows users to customize Logtalk without forcing them to edit the back-end Prolog configuration files, which often change between releases. This allows users to easily update to a new Logtalk version without the tedious work of reapplying changes to the default values of compiler flags to the new versions of the configuration files. Updated scripts and installers automatically will take care, for future releases, of preserving any existing settings file found when updating the Logtalk user folder.

Although the new settings files feature is easy to describe, a lot of work went into implementation and testing, thanks to the lack of Prolog standards for operating-system access. This includes basic functionality such as finding out the startup directory, opening a file in the current directory, and accessing operating-system environment variables. Something that should be implemented and tested during an afternoon, took me one week of work. One week that would be better spent working on e.g. the Logtalk libraries. In the end, I got settings file working for most back-end Prolog compilers on POSIX systems and, for some of them, also on Windows. Not ideal but hard to do better giving the back-end Prolog compiler limitations.

Lack of feature-parity between back-end Prolog compilers is always a problem. It turns what should be simple documentation in long lists of exceptions. Some users, completely oblivious of the nightmare that is writing portable Prolog code, end up blaming Logtalk for a limited feature set that pales when compared with recent programming languages. I’m quite tired of dealing with this portability problems. Therefore, future Logtalk releases will cut down on the number of compatible Prolog compilers. Hopefully, this will speed up future development and will enable more straightforward implementations of new functionality.


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.


Writing portable Logtalk applications

Portable Logtalk applications often need to call built-in predicates whose name, arguments, and semantics differ for each supported back-end Prolog compiler. This is quite frequent as the current Prolog ISO standard only covers basic functionality. A common solution for these cases is to define a protocol abstracting common functionality and to implement this protocol in a set of objects, one for each back-end Prolog compiler. An alternative solution is to use Logtalk conditional compilation directives. As also found in some Prolog compilers (e.g. ECLiPSe, SWI-Prolog, or YAP), Logtalk supports if/1, elif/1, else/0, and endif/0 directives. The arguments of the if/1 and elif/1 directives are goals that are evaluated at compilation time, switching on and off the compilation of blocks of code. Together with the prolog read-only compiler flag, it is easy to define a single object encapsulating code for different back-end Prolog compilers. For example, assuming that your portable Logtalk application (running on GNU Prolog, SWI-Prolog, and YAP) needs access to the CPU time in seconds, you could write:

:- object(timing).
 
    :- public(cpu_time/1).
    :- mode(cpu_time(-number), one).
    :- info(cpu_time/1, [
        comment is 'Current CPU time in seconds',
        argnames is ['Seconds']]).
 
    :- if(current_logtalk_flag(prolog, gnu)).
    cpu_time(Seconds) :-
        cpu_time(Miliseconds),
        Seconds is Miliseconds/1000.
 
    :- elif(current_logtalk_flag(prolog, swi)).
    cpu_time(Seconds) :-
        Seconds is cputime.
 
    :- elif(current_logtalk_flag(prolog, yap)).
    cpu_time(Seconds) :-
        statistics(cputime, [Miliseconds, _]),
        Seconds is Miliseconds/1000.
 
    :- else.
    cpu_time(_) :-
        throw(error(resource_error(predicate), cpu_time/1)).
 
    :- endif.
 
:- end_object.

A second example, cc, can be found on the current Logtalk distribution.

The conditional compilation directives can be used anywhere, as long as they encapsulate groups of directives or predicate clauses. Both solutions, using an object per back-end Prolog compiler or a single object with conditional compilation directives, have their pros and cons. The hard work, however, is usually to find the best abstraction subsuming the diversity of implementations.


Porting Prolog programs to Logtalk

I’m currently porting John Fletcher’s XML Parser to Logtalk. I’m also considering porting P-FLAT, Artur Dias and Michel Wermelinger’s toolkit for formal languages and automata theory. The ports are easy although more time consuming that I wished for. Given that Logtalk assumes a ISO Prolog compliant compiler underneath, the original Prolog programs must have all proprietary Prolog calls replaced by their ISO counterparts; that’s the price you pay for the broad compatibility of Logtalk with most Prolog compilers in use today.

It’s clear to me, given these current and past port exercises, that broad sharing of Prolog code between implementations suffers from both weak ISO Prolog standards and lack of knowledge of the current standards by Prolog programmers. Work on improving the current standards is underway but also undermanned. Lack of knowledge of the current standards can be partially explained by the simple fact that the ISO documents are only available for a fee (there is also a nice book on the ISO Prolog standard by P. Deransart, A. Ed-Dbali, and L. Cervoni). Another possible explanation is that most Prolog programmers do not feel a need for writing portable code. Or simply lack the perception that their code will not run and, in some cases, will not even compile, when using another Prolog implementation. This is as much as a problem with programmers as a problem with the Prolog language itself. People working with Java, Python, C#, or PHP do not face the kind of compatibility problems that plague Prolog programming.

We have a very rich Prolog ecosystem with more than two dozen implementations currently available. Even if we count only the most popular ones, their number is still close to a dozen. However, Prolog is nowadays little more than a niche language. Lack of code sharing means that Prolog programmers cannot bootstrap their applications without being trapped in some proprietary implementation, even for basic tasks. Lack of libraries and bindings for popular APIs makes choosing Prolog as an industrial tool a risky proposition, despite all the advantages when compared with other languages. Is the Prolog future bleak? I like to think that you and me can make a difference. How? By demanding better ISO Prolog compliance from Prolog implementers, by working with our local standards committee, by regularly rotating Prolog compilers when programming. Current standardization efforts include DCGs, Globals, and Threads. A revision to the core standard is also underway, fixing typos, omissions, and hopefully adding some missing built-in predicates that should have been there from the start. If you currently have an itch that needs scratching, why not find other programmers facing the same problems (Prolog mailing lists and the comp.lang.prolog newsgroup are good places to start) and write a draft proposal?