1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
|
\chapter{Compatibility with other Prolog dialects}
\label{sec:dialect}
\index{YAP,prolog}%
\index{XSB,prolog}%
\index{SICStus,prolog}%
\index{IF,prolog}%
\index{portable,prolog code}%
This chapter explains issues for writing portable Prolog programs. It
was started after discussion with Vitor Santos Costa, the leading
developer of YAP Prolog\footnote{\url{http://yap.sourceforge.net/}} YAP
and SWI-Prolog have expressed the ambition to enhance the portability
beyond the trivial Prolog examples, including complex libraries
involving foreign code.
Although it is our aim to enhance compatibility, we are still faced
with many incompatibilities between the dialects. As a first step both
YAP and SWI will provide some instruments that help developing portable
code. A first release of these tools appeared in SWI-Prolog 5.6.43.
Some of the facilities are implemented in the base system, others in the
library \pllib{dialect.pl}.
\begin{itemize}
\item The Prolog flag \prologflag{dialect} is an unambiguous and fast way to
find out which Prolog dialect executes your program. It has the
value \const{swi} for SWI-Prolog and \const{yap} on YAP.
\item The Prolog flag \prologflag{version_data} is bound to a term
\term{swi}{Major, Minor, Patch, Extra}
\item Conditional compilation using \exam{:- if(Condition)} \ldots
\exam{:- endif} is supported. See \secref{conditionalcompilation}.
\item The predicate expects_dialect/1 allows for specifying for
which Prolog system the code was written.
\item The predicates exists_source/1 and source_exports/2 can be
used to query the library content. The require/1 directive can
be used to get access to predicates without knowing their location.
\item The module predicates use_module/1, use_module/2 have been
extended with a notion for `import-except' and `import-as'. This
is particularly useful together with reexport/1 and reexport/2 to
compose modules from other modules and mapping names.
\item Foreign code can expect \const{__SWI_PROLOG__} when compiled
for SWI-Prolog and \const{__YAP_PROLOG__} when compiled on YAP.
\end{itemize}
\begin{description}
\directive{expects_dialect}{1}{+Dialect}
This directive states that the code following the directive is written
for the given Prolog \arg{Dialect}. See also \prologflag{dialect}. The
declaration holds until the end of the file in which it appears. The
current dialect is available using prolog_load_context/2.
The exact behaviour of this predicate is still subject to discussion. Of
course, if \arg{Dialect} matches the running dialect the directive has
no effect. Otherwise we check for the existence of
\term{library}{dialect/Dialect} and load it if the file is found.
Currently, this file has this functionality:
\begin{itemize}
\item Define system predicates of the requested dialect we do not
have.
\item Apply goal_expansion/2 rules that map conflicting predicates
to versions emulating the requested dialect. These expansion rules
reside in the dialect compatibility module, but are
applied if prolog_load_context(dialect, Dialect) is active.
\item Modify the search path for library directories, putting
libraries compatible with the target dialect before the native
libraries.
\item Setup support for the default filename extension of the
dialect.
\end{itemize}
\predicate{source_exports}{2}{+Spec, +Export}
Is true if source \arg{Spec} exports \arg{Export}, a predicate
indicator. Fails without error otherwise.
\end{description}
\section{Some considerations for writing portable code}
\label{sec:portabilitystrategies}
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.
\begin{itemize}
\item Probably the best reason for this is that it allows to
define minimal semantics required by the application
for the portability predicates. Such functionality can often
be mapped efficiently to the target dialect. Contrary, if
code was written for dialect $X$, the defined semantics are
those of dialect $X$. Emulating all extreme cases and full
error handling compatibility may be tedious and result in a
much slower implementation than needed. Take for example
call_cleanup/2. The SICStus definition is fundamentally
different from the SWI definition, but 99\% of the applications
just want to make calls like below to guarantee \arg{StreamIn}
is closed, even if \nopredref{process}{1} misbehaves.
\begin{code}
call_cleanup(process(StreamIn), close(In))
\end{code}
\item As a drawback, the code becomes full of \textit{my_call_cleanup},
etc.\ and every potential portability conflict needs to be
abstracted. It is hard for people who have to maintain such code
later to grasp the exact semantics of the \textit{my_*} predicates
and applications that combine multiple libraries using this
compatibility approach are likely to encounter conflicts between
the portability layers. A good start is not to use \textit{my_*},
but a prefix derived from the library or application name or names
that explain the intended semantics more precisely.
\item Another problem is that most code is initially not written
with portability in mind. Instead, ports are requested by users
or arise from the desire to switch Prolog dialect. Typically, we
want to achieve compatibility with the new Prolog dialect with
minimal changes, often keeping compatibility with the original
dialect(s). This problem is well known from the C/Unix world
and we advise anyone to study the philosophy of
\href{http://www.gnu.org/software/autoconf/}{GNU autoconf}, from
which we will illustrate some highlights below.
\end{itemize}
The GNU autoconf suite, known to most people as \program{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 \exam{:- if(Condition)} as explained in
\secref{conditionalcompilation}. The situation appears similar.
The important lesson learned from GNU autoconf is that the \emph{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 \program{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:
\begin{code}
:- if(\+current_predicate(_, compare(_,_,_))).
compare(<, Term1, Term2) :-
Term1 @< Term2, !.
compare(>, Term1, Term2) :-
Term1 @> Term2, !.
compare(=, Term1, Term2) :-
Term1 == Term2.
:- endif.
\end{code}
This code is \textbf{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.
\begin{code}
:- 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.
\end{code}
\section{Notes on specific dialects}
\label{sec:dialect-notes}
The level of maturity of the various dialect emulation implementations
varies enormously. All of them have been developed to realise
portability for one or more, often large, programs. This section
provides some notes on emulating a particular dialect.
\subsection{Notes on specific dialects}
\label{sec:dialect-xsb}
\href{http://xsb.sourceforge.net/}{XSB} Prolog compatibility emerged
from a project to integrate XSB's advanced tabling support in SWI-Prolog
(see \secref{tabling}). This project has been made possible by
\href{https://kyndi.com/}{Kyndi}.\footnote{This project was initiated by
Benjamin Grosof and carried out in cooperation with Theresa Swift, David
S. Warren and Fabrizio Riguzzi.} The XSB dialect implementation has been
created to share as much as possible of the XSB test suite as well as
some larger programs to evaluate both tabling implementations. The
dialect emulation was extended to support
\href{https://github.com/cmu-sei/pharos}{Pharos}.\footnote{Pharos was
used to evaluate \jargon{incremental tabling}
(\secref{tabling-incremental}), a protect with Edward Schwatz and Cory
Cohen from CMU}.
Emulating XSB is relatively complicated due to the large distance from
the Quintus descendant Prolog systems. Notably XSB's name based module
system is hard to map on SWI-Prolog's predicate based module system. As
a result, only non-modular projects or projects with basic usage of
modules are supported. For the development of new projects that require
modules more advanced module support we suggest using
\href{https://logtalk.org/}{Logtalk}.
\subsubsection{Loading XSB source files}
\label{sec:xsb-source}
SWI-Prolog's emulation of XSB depends on the XSB preferred file name
extension \fileext{P}. This extension is used by
\pllib{dialect/xsb/source} to initiate a two phase loading process based
on term_expansion/2 of the virtual term \const{begin_of_file}.
\begin{enumerate}
\item In the first phase the file is read with XSB compatible
operator declarations and all directives (:- Term) are extracted.
The directives are used to determine that the file defines a
module (iff the file contains an export/1 directive) and construct
a SWI-Prolog compatible module declaration. As XSB has a two phase
compiler where SWI has a single phase compiler, this is also used to
move some directives to the start of the file.
\item The second phase loads the file as normal.
\end{enumerate}
To load a project in both XSB and SWI-Prolog it is advised to make sure
all source files use the \fileext{P} file name extension. Next, write a
SWI-Prolog loader in a \fileext{pl} file that contains e.g.,
\begin{code}
:- use_module(library(dialect/xsb/source)).
:- [main_file].
\end{code}
It is also possible to put the able use_module/1 directive in your
personal initialization file (see \secref{initfile}), after which
XSB files can be loaded as normal SWI-Prolog files using
\begin{code}
% swipl file.P
\end{code}
\index{gpp,XSB proprocessor}%
XSB code may depend on the \program{gpp} preprocessor. We do not
provide \program{gpp}. It is however possible to send XSB source
files through \program{gpp} by loading \pllib{library/dialect/xsb/gpp}.
This require \program{gpp} to be accessible through the environment
variable \env{PATH} or the file_search_path/2 alias \const{path}.
We refer to the \const{gpp} library for details.
\subsection{The XSB import directive}
\label{sec:xsb-import}
The XSB import directive takes the form as below.
\begin{code}
:- import p/1, q/2, ... from <lib>.
\end{code}
This import directive is resolved as follows:
\begin{itemize}
\item If the referenced library is found as a local file,
it is loaded and the requested predicates are imported.
\item Otherwise, the referenced library is searched for in
the \file{dialect/xsb} directory of the SWI-Prolog library.
If found, the predicates are imported from this library.
\item The referenced predicates are searched for in SWI-Prolog
built-in predicates and the SWI-Prolog library. If found, they are
made available if necessary.
\end{itemize}
|