JPL 3.x Java API overview


Table of Contents


Introduction

The JPL 3.0.1 Java-calls-Prolog API provides a set of classes that hide almost all of the messy detail in the Low-Level Interface.  It is less flexible than the Low-Level Interface, but it also has less of a learning curve, and in many ways is more natural and Prolog-like than the Low-Level Interface.

The Java package jpl contains all of the classes in this interface.  None of the classes correspond with any of the data types in the Prolog Foreign Language Interface (FLI).
 

The Class Hierarchy

The API consists of the following class hierarchy:

Term
|
+--- Variable
|
+--- Compound
| |
| +--- Atom
|
+--- Integer
|
+--- Float

Query

JPLException
|
+-- PrologException

Term is an abstract class: only its subclasses can be instantiated.

Each instance of Query contains a Term (denoting the goal which is to be proven), and much more besides.

Each instance of Compound has a (java.lang.String) name and an array of (Term) arguments (it must have at least one).

Atom is a specialisation of Compound with zero arguments.
 

Initializing Prolog

The jpl.JPL class initializes the Prolog VM (e.g. libpl.dll in Win32), if necessary, when the first Query is activated, using default parameter values.  Before initialization takes place, these default values can be read, and altered.

public String[] getDefaultInitArgs();
public void setDefaultInitArgs(String[] args);

After initialization, the parameter values which were actually used can be read.

public String[] getActualInitArgs();

(This method returns null if initialization has not occurred, and thus it can be used as a test.)
This allows Java library classes to employ JPL without placing any burden of initialization upon the applications which use them.  It can also ensure that the Prolog VM is initialized only if and when it is needed.

Explicit initialization is supported as in JPL 1.0.1:

public void init();
public void init( String args[] );

Java code which requires a Prolog VM to be initialized in a particular way can check whether initialization has already occurred: if not, it can specify parameters and force it to be attempted; if so, it can retrieve and check the initialisation parameters actually used, to determine whether the initialization meets its requirements.
 
This version of JPL does not support reinitialization of a Prolog VM.

For details about the legal parameter values, see your local Prolog documentation.  Most users will rely on automatic initialization.
 

Creating Terms

The Term-based classes in the jpl package are best thought of as a structured concrete syntax for Prolog terms: they do not correspond to any particular terms within the Prolog engine; rather, they are a means for constructing queries which can called within Prolog, and they are also a means for representing (and exploring) the results of such calls.
Term instances are never changed by any activity within the Prolog engine: indeed; it doesn't know of their existence.
The Term class is abstract, so it cannot be directly instantiated; to create a Term, create an instance of one of its five subclasses.

Note. A Term in the jpl packagee is not to be confused with a term_t in the jpl.fli package.  The latter has an important internal role in managing state in the Prolog stack; the former is just a data structure in the Java heap.

Atoms

An Atom is a Compound with zero arguments.  To create an Atom, pass a (String) name to its constructor:

Atom aristotle = new Atom("aristotle");
Atom alexander = new Atom("alexander");
Note.  Two Atoms by the same name are effectively identical.  Feel free to reuse Atom instances when constructing compound Terms.
Note.  The name in an Atom need not be lower case: it can be any UTF-8 string (?).

The Atom class inherits Compound's name() accessor to obtain the name of the Atom (it also inherits Compound's arity() accessor, but this always returns zero for an Atom)Atom's toString() method yields a String form of the atom's name which is quoted, iff necessary, according to Prolog source text syntax, and can thus be used when constructing fragments of Prolog source text, e.g. new queries.

Variables

Variables have identifying names, which must comply with conventional Prolog source text syntax.

Variable X = new Variable("X"); // a regular variable
Variable X = new Variable("_"); // an "anonymous" variable
Variable X = new Variable("_Y"); // a "dont-tell-me" variable, whose bindings we don't want to know

Integers

An Integer is a specialized Term that holds a Java long value.  This class corresponds to the Prolog integer type (SWI-Prolog integers are 32-bit for now, but we are looking ahead and beyond...).

jpl.Integer i = new jpl.Integer(5);

Be careful to avoid confusion with java.lang.integer, e.g. by always qualifying the class name as in the example above.

The jpl.Integer class has an intValue() accessor to obtain the int value of an instance, and also longValue(), floatValue() and doubleValue() (just like java.lang.Integer has).

Floats

A Float is a specialized Term that holds a Java double value.  This class corresponds to the Prolog float type (SWI-Prolog floats are 64-bit ISO/IEC), on which arithmetic operations can be performed.

jpl.Float f = new jpl.Float(3.14159265);

As with integers, avoid confusion between jpl.Float and java.lang.Float.

The jpl.Float class has a doubleValue() accessor to obtain the double value of an instance, and also a floatValue() accessor.

Compounds

A Compound is a Term that contains a name and a sequence (array) of Term arguments, as reflected in this class's constructor:

Compound teacher_of = new Compound(
"teacher_of",
new Term[] {
new Atom("aristotle"),
new Atom("alexander")
}
);

Note the use of Java's anonymous array syntax

new Term[] { ..., ... }

to specify the arguments (any quantity >= 1) of the Compound.

In this example, the Java variable teacher_of refers to a Compound instance, which represents the Prolog term teacher_of(aristotle,alexander).

Note. Care should be taken in creating Compound Terms, especially if Variable references are used.  For example, the following construction:
Variable X = new Variable();
Variable Y = new Variable();
Compound father_of = new Compound( "teacher_of", new Term[]{X,Y});
corresponds with the Prolog term teacher_of(X,Y), whereas
Variable X = new Variable();
Compound father_of = new Compound( "teacher_of", new Term[]{X,X});
corresponds with the Prolog term teacher_of(X,X), two terms that can resolve very differently depending on the Prolog database.  The general rule of thumb should be, reuse Term references that are or contain Variables only if you know that that is what you mean.

To obtain the (String) name of a Compound, use the name() accessor method.

public String name();

To obtain the arity of a Compound, use the arity() accessor method.

public int arity();

To obtain an array of a Compound's arguments, use the args() accessor method.

public Term[] args();

To obtain the ith argument of a compound (numbered from 1), use the arg() accessor method (with an int parameter value between 1 and Arity inclusive).

public Term arg( int i);

To obtain the ith argument of a compound (numbered from 0), use the arg0() accessor method (with an int parameter value between 0 and Arity-1 inclusive).

public Term arg0( int i);

Queries

A Query contains a Term, representing a Prolog goal:

Term goal = new Compound( "teacher_of", new Term[]{new Atom("aristotle"),new Atom("alexander")});
Query q = new Query( goal );

The Query q in this example represents the Prolog query

?- teacher_of(aristotle,alexander).

The Util Class

The Util class provides various static utility methods for managing JPL Terms.

Term termArrayToList( Term t[])
Term[] listToTermArray( Term t)
Term[] bindingsToTermArray( Hashtable bs)

Querying Prolog

To ask the Prolog engine a query via the High-Level Interface, one first constructs a Query instance, as in the above example, and then uses the java.util.Enumeration interface, which the Query class implements, to obtain solutions (where a "solution" is what is known in logic programming jargon as a substitution, which is a collection of bindings, each of which relates one of the Variables within the Query's goal to a Term representation of the Prolog term to which the corresponding Prolog variable was bound by the proof).

public interface Enumeration {
public boolean hasMoreElements();
public Object nextElement();
}

The hasMoreElements() method can be used to determine whether a Query has any (or any further) solutions.  In the above example, the method call

q.hasMoreElements()

returns true if the Prolog query teaches(aristotle,alexander) is provable, and false otherwise.  In this example, the Prolog query is a ground term, so the "solution" to the Query is merely a truth value, and is given by the hasMoreElements() method.

Where a Query's goal contains Variables, on the other hand, its execution yields a sequence of bindings of these Variables to Terms.  The High-Level interface uses a java.util.Hashtable to represent these bindings; the Objects in the table are Terms, keyed (uniquely) by Variable instances.

For example, to print all of Aristotle's pupils, i.e., all the bindings of X which satisfy teaches(aristotle,X), one could write

Variable X = new Variable();
Query q = new Query( "teaches", new Term[]{new Atom("aristotle"),X});
while ( q.hasMoreElements() ) {
Hashtable binding = (Hashtable) q.nextElement();
Term t = (Term) binding.get( X);
System.out.println( t);
}
Note.  If a Query's goal contains no variables (i.e. it is "ground"), the Query. nextElement() method will still return a Hashtable for each solution, although each table will be empty.
Note.  If a Query's goal contains more than one occurrence of some Variable, then each  solution Hashtable will have only one binding for that Variable.

For convenience, the Query class provides a hasMoreSolutions() and nextSolution() method with the following signatures:

public boolean hasMoreSolutions();
public Hashtable nextSolution();

Using the nextSolution() method avoids having to cast the result of the nextElement() method to Hashtable.

Obtaining one Solution

Often, you'll just want just the first solution to a query.  The Query class provides a method for this:

public Hashtable oneSolution();

If the Query has no solutions, this method returns null; otherwise, a non-null return indicates success.  If the Query is a ground query (i.e. contains no variables), the returned Hashtable will be empty (i.e. will contain no bindings).

Obtaining all Solutions

You may want all solutions to a query.  The Query class provides a method for this:

public Hashtable[] allSolutions();

The returned array will contain all the Query's solutions, in the order they were obtained (as with Prolog's findall/3, duplicates are not removed).  If the Query has no solutions, this method returns an empty array (N.B. not null as in JPL 1.0.1).

Discovering whether a query has any solutions

Sometimes an application is interested only in whether or not a query is provable, but not in any details of its possible solutions.  The Query class provides the hasSolution method for this common special case:

public boolean hasSolution();

This method is equivalent to (but sometimes more efficient than) calling oneSolution and asking whether the return value is non-null (i.e. whether the query succeeded).

Terminating Queries

Queries terminate automatically when the hasMoreSolutions() method returns false, and once a Query is terminated, another can be started.  Unfortunately, the Prolog engine is currently such that it can handle only one query at a time.  As a result, it is not possible, in the High-Level Interface, to ask two different Query objects whether they have any solutions without first exhausting all of the solutions of one.  Therefore, programmers must take care to ensure that all solutions are exhausted before starting new queries.  This has particular importance in multi-threaded contexts, but it can also present difficulties even in single-threaded programs.  See the Multi-Threaded Queries section for a discussion of how to manage Queries in multi-threaded contexts.

To terminate a Query before all of its solutions have been exhausted, use the rewind() method:

public void rewind();

This method stops a Query, setting it back into a state where it can be restarted.  It also permits other queries to be started.  Here is an example in which the first three solutions to the Query are obtained:

Query query = // obtain Query somehow
for ( int i = 0; i < 3 && query.hasMoreSolutions(); ++i ){
Hashtable solution = query.nextSolution();
// process solution...
}
query.rewind();

You may call rewind() on an inactive Query without ill-effect, and you should always call rewind if you have not exhausted all solutions to a Query.

If you are using the query(), oneSolution(), or allSolutions() methods, you need not worry about rewinding the Query; it is done automatically for you.
 

Multi-Threaded Queries

The Prolog engine can only have one query open at a time.  This presents difficulties for multi-threaded programs in which the programmer has no control over when Queries are executed.  JPL makes as much of the High-Level Interface thread-safe as it can.  Unfortunately, the programmer must take responsibility in a limited set of circumstances to ensure that all calls to the High-Level Interface are thread safe.

It is worth noting that if the programmer confines use of Query methods to hasSolution(), oneSolution(), and allSolutions(), that subset of the Query interface is thread-safe.  For many programmers, these methods suffice.  However, if the hasMoreSolutions(), hasMoreElements(), nextSolution(), nextElement(), or rewind() methods are explicitly invoked, thread-safety is lost.  The problem is that while the blocks of these programs are synchronized so that in effect no two Query objects can invoke any of these methods concurrently, there is nothing that prevents a Query object in one thread from calling one of these methods, and another Query object in a different thread from calling this same method, or even another that could produce indeterminate results.

The Query class, however, does make synchronization around these methods possible by providing a reference to the monitor object that locks competing threads from executing critical code.  The reference is obtained by the static method

public static Object lock();

Thus, programmers can wrap calls to these non-thread-safe methods in synchronized blocks, using the lock object to prevent other threads from entering any of these methods.  To write a thread-safe loop to process all of a Query's solutions, for example, one might write

Query query = // obtain Query
synchronized ( Query.lock() ){
while ( query.hasMoreSolutions() ){
Hashtable solution = query.nextSolution();
// process solution...
}
}


Note that the query(), oneSolution(), and allSolutions() methods effectively do the same as the above code snippet, so there is no need to explicitly synchronized on the Query's monitor object when these methods are called.
 

Exceptions

The JPL package provides fairly crude exception handling.  The base class for all JPL Exceptions is JPLException, which is a java.lang.RuntimeException (and hence need not be declared), and which will be thrown in the absence of any other kind of exception that can be thrown, usually as the result of some programming error.  Converting the exception to a java.lang.String should provide some descriptive information about the reason for the error.  All other JPL excpetion classes extend this class.  Currently there are two, the QueryInProgressException class and the PrologException class.

A QueryInProgressException is thrown when a Query is opened while another is in progress; this exception can be caught in multi-threaded situations, but a better strategy for managing multi-threaded situations is discussed in the Multi-Threaded Queries section.  If you obey the rules discussed in this section, you should have no reason to catch this exception.

A PrologException is thrown either during execution of a Prolog built-in predicate or by an explicit call, by Prolog application code, of the Prolog predicate throw/1.

There is currently no means of gracefully handling exceptions caused by malformed parameters (e.g., undefined predicates) passed through the High-Level Interface to the Prolog engine (?).
 

Debugging

Each Term type (together with the Query class) supports an implementation of toString() which returns a more-or-less familiar Prolog textual representation of the Term or Query.

Sometimes, however, this information is not sufficient, so we have provided a method debugString() which provides a more verbose and explicit representation, including the types (atom, integer etc) of each term and subterm.

In general, Term and Query instances are represented in the form (type data), where type is the name of the type (e.g., Atom, Compound, Tuple, etc.), and data is a representation of the contents of the Term.  For example, if the Term is an Atom, the data is the Atom's name.  The arguments of Compounds are represented by comma-separated lists within square brackets ('[' ']').

Viewing the structure of a Term or Query can be useful in determining whether an error lies on the Prolog or Java side of your JPL applications.

Perhaps better still, Term implements (in a basic but adequate way) the javax.swing.TreeModel interface, and its display() method creates a JFrame containing a browseable JTree representation of the term.
 

Version information

To obtain the current version of JPL you are using, you may obtain a reference to the jpl.Version static instance of the JPL class by calling the JPL.version() static method.  This will return a jpl.Version structure, which has the following final fields:

package jpl;
public class Version {
public final int major; // e.g. 2
public final int minor; // e.g. 0
public final int patch; // e.g. 2
public final java.lang.String status; // e.g. "alpha"
}

You may wish to use this class instance to obtain fine-grained information about the current JPL version, e.g.

if ( JPL.version().major == 2 ) {

You may also simply call the version_string() static method of the jpl.JPL class.  This will return a java.lang.String representation of the current JPL version.

The version string can be written to the standard output stream by running the main() method of the jpl.JPL class.

linux% java jpl.JPL
JPL 2.0.2-alpha

What's Missing

The current implementation of the High-Level Interface lacks support for modules, and for multiple Prolog engines.
 
 


up   prev  next  API