Did you know ... | Search Documentation: |
Some considerations for writing portable code |
The traditional way to write portable code is to define custom predicates for all potentially non-portable code and define these separately for all Prolog dialects one wishes to support. Here are some considerations.
call_cleanup(process(StreamIn), close(In))
The GNU autoconf suite, known to most people as configure, was
an answer to the frustrating life of Unix/C programmers when Unix
dialects were about as abundant and poorly standardised as Prolog
dialects today. Writing a portable C program can only be achieved using
cpp, the C preprocessor. The C preprocessor performs two tasks: macro
expansion and conditional compilation. Prolog realises macro expansion
through term_expansion/2
and goal_expansion/2.
Conditional compilation is achieved using :- if(Condition)
as explained in
section 4.3.1.2.
The situation appears similar.
The important lesson learned from GNU autoconf is that the last resort for conditional compilation to achieve portability is to switch on the platform or dialect. Instead, GNU autoconf allows you to write tests for specific properties of the platform. Most of these are whether or not some function or file is available. Then there are some standard tests for difficult-to-write-portable situations and finally there is a framework that allows you to write arbitrary C programs and check whether they can be compiled and/or whether they show the intended behaviour. Using a separate configure program is needed in C, as you cannot perform C compilation step or run C programs from the C preprocessor. In most Prolog environments we do not need this distinction as the compiler is integrated into the runtime environment and Prolog has excellent reflexion capabilities.
We must learn from the distinction to test for features instead of platform (dialect), as this makes the platform-specific code robust for future changes of the dialect. Suppose we need compare/3 as defined in this manual. The compare/3 predicate is not part of the ISO standard, but many systems support it and it is not unlikely it will become ISO standard or the intended dialect will start supporting it. GNU autoconf strongly advises to test for the availability:
:- if(\+current_predicate(_, compare(_,_,_))). compare(<, Term1, Term2) :- Term1 @< Term2, !. compare(>, Term1, Term2) :- Term1 @> Term2, !. compare(=, Term1, Term2) :- Term1 == Term2. :- endif.
This code is much more robust against changes to the intended dialect and, possibly at least as important, will provide compatibility with dialects you didn't even consider porting to right now.
In a more challenging case, the target Prolog has compare/3, but the semantics are different. What to do? One option is to write a my_compare/3 and change all occurrences in the code. Alternatively you can rename calls using goal_expansion/2 like below. This construct will not only deal with Prolog dialects lacking compare/3 as well as those that only implement it for numeric comparison or have changed the argument order. Of course, writing rock-solid code would require a complete test-suite, but this example will probably cover all Prolog dialects that allow for conditional compilation, have core ISO facilities and provide goal_expansion/2, the things we claim a Prolog dialect should have to start writing portable code for it.
:- if(\+catch(compare(<,a,b), _, fail)). compare_standard_order(<, Term1, Term2) :- Term1 @< Term2, !. compare_standard_order(>, Term1, Term2) :- Term1 @> Term2, !. compare_standard_order(=, Term1, Term2) :- Term1 == Term2. goal_expansion(compare(Order, Term1, Term2), compare_standard_order(Order, Term1, Term2)). :- endif.