
OL(P): Object Layer for Prolog -- User Manual
Version 1.1 for SICStus Prolog and QUINTUS Prolog


Object Layer for Prolog, OL(P), provides an object-oriented
structuring and reuse mechanism for Prolog. The primary goal of OL(P)
is to provide this mechanism in a way that is performance and
semantics preserving. This is achieved by compiling the
object-oriented layer to Prolog without introducing any side-effects.
Thus, Prolog's computational model is retained, and meta-programs can
easily be adapted from Prolog. Full Prolog can be used in OL(P), and
different Prolog variants are allowed as base languages.

OL(P) takes the view of objects as collections of predicates (called
methods). To Prolog, OL(P) 1.1 adds objects with methods, data
encapsulation, instances, and multiple inheritance. Object methods can
access Prolog predicates and vice versa. A future version will add
optimization based on unfolding over objects. The OL(P) incremental
compiler translates OL(P) programs to Prolog programs that don't need
runtime interpretation (e.g., no search is needed for inheritance).
With the optimization, compiled OL(P) programs will have the
performance of partially evaluated Prolog programs.

OL(P) 1.1 comes with easy installation for SICStus Prolog and QUINTUS
Prolog, documentation, simple built-in project management, some
libraries, and example programs.  The optimizer is in a conceptual
phase, and a declarative debugger for full OL(P) (a superset of full
Prolog) is in preparation. (No guarantee that these will become
available in the near future.)

Please send comments and bug reports to <fromherz@parc.xerox.com>.

Markus P.J. Fromherz


  Copyright (c) 1993 Markus P.J. Fromherz.  All Rights Reserved.
  Copyright (c) 1993 Xerox Corporation.  All Rights Reserved.

  Use, reproduction, preparation of derivative works, and distribution
  of this software is permitted, but only for non-commercial research or
  educational purposes. Any copy of this software or of any derivative
  work must include both the above copyright notices of Markus P.J.
  Fromherz and Xerox Corporation and this paragraph.  Any distribution
  of this software or derivative works must comply with all applicable
  United States export control laws. This software is made available AS
  IS, and XEROX CORPORATION DISCLAIMS ALL WARRANTIES, EXPRESS OR
  IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND
  NOTWITHSTANDING ANY OTHER PROVISION CONTAINED HEREIN, ANY LIABILITY
  FOR DAMAGES RESULTING FROM THE SOFTWARE OR ITS USE IS EXPRESSLY
  DISCLAIMED, WHETHER ARISING IN CONTRACT, TORT (INCLUDING NEGLIGENCE)
  OR STRICT LIABILITY, EVEN IF XEROX CORPORATION IS ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGES.


This manual gives an introductory overview and examples for OL(P) programs.

Table of contents of this file:

   1. Projects and Files
   2. Objects
   3. Declarations, Inheritance
   4. Methods
   5. Examples
   6. Instances
   7. OL(P) in SICStus Prolog
   8. Projects
   9. Meta-Programs for OL(P) Programs
  10. Operator Declarations in the OL(P) System


1. Projects and Files
---------------------

An OL(P) program consists of a set of OL(P) files; the set is called a project.
Each OL(P) file may contain objects, predicates, and directives. Usually, files
will be based on Prolog and should be named `File.pl.ol'. OL(P) will compile
these files to Prolog files `File.pl'. In addition, a project file `Project.pl'
is generated which contains the project's object-layer information.

Other Prolog-like base languages may be used. Several base languages may be
used in the same project, but a file has to be written entirely in the same
base language. For example, some files (i.e. their objects and predicates) may
be based on Prolog, while other files may be based on a constraint or
concurrent logic language. A file's base language is indicated by its
extension, e.g., `File.cc.ol'. See Base Language Configuration in the README
file.

Don't be confused by the term "base language". If you are using only Prolog, it
simply means "Prolog" for you.


2. Objects
----------

OL(P) files may contain objects, predicates, and directives, the latter two
defined as usually in the base language. An object consists of methods and
declarations, which are enclosed in begin and end statements. The begin
statement can be one of

   object Name.
   object Name is_a SuperObjects.

where `Name' is the object's name, and `SuperObjects' are one or more object
names, separated by commas. In the first case, object `Name' is a subobject of
the root object `object', in the second case, it is a subobject of the
`SuperObjects'. The object's end statement is

   end_object Name.


3. Declarations, Inheritance
----------------------------

Declarations are directives in an object and define its interface, inheritance,
and optimization characteristics (not in version 1.1). The declaration

   :- publish Methods.

publishes the `Methods', where `Methods' are one or more `Name/Arity' pairs,
separated by commas. A `Name/Arity' pair declares this method public, i.e. it
can be called by other objects (and the base language), and it is inherited by
subobjects.

The declaration

   :- override Methods.

declares the `Methods' as overridden in this object; `Methods' are one or more
`Name/Arity' pairs, separated by commas. Overriding methods means that these
methods are defined in superobjects and would be inherited, but are redefined
in this object. All methods published, inherited or overridden in an object are
public for that object; all other methods are private to the object. (Sofar,
a goal with a message from the outside to a private method simply fails; only
in the optimized program version will an exception be raised.)

A word about inheritance: an object inherits all public methods from its
superobjects (and thus from the superobjects' superobjects and so on). If a
method `Name/Arity' can be inherited from several superobjects, the method
first found in a left-to-right, depth-first search up the inheritance tree
(along the `SuperObjects') is taken, except if the found object has a
subobject found later which also defines the method, in which case this more
specialised version is taken. See example below. (Note that, except for meta-
variables, the inheritance search is done at compile-time, not at run-time.
The idea to refine the inheritance using the most specialised version of a
method is due to T. Sj\"oland, Using SICStus Objects in the Design of
Graphical User Interfaces, SICS, TR R92:10.)


4. Methods
----------

Methods are defined just like predicates in the base language, except that
goals are either Prolog goals or objects goals. Prolog goals are written as
usually. Object goals are messages sent to objects: `Object::Message' sends
`Message' to `Object'; `Object' is an object name or a pseudo-variable, and
`Message' is a goal to be called in the context of this object.
   If `Object' is an object name, the corresponding method defined in `Object'
or inherited there from a superobject is called; the method has to be public.
If the corresponding method in `Object' is overridden there, the overriding
method (i.e. the one defined in `Object') is called.

`Object' in a method's goal can also be one of the pseudo-variables `self',
`this', `super', or `prolog'.
   `self' refers to the context in which the current method will be evaluated.
For example, if the method is inherited by a subobject and called there, that
object is the method's context for that call. (`::Message' is the same as
`self::Message'.)
   `this' simply is a replacement for the name of the object the current method
is defined in. Messages for private methods can only be sent within the object
using `this' or the actual object name. Note that a message for a public method
using `this' "freezes" the context for the execution of the method; if a method
is published, `self' should always be used.
   `super' refers to the superobject from which a method has been inherited.
This can be used if a method has been overridden: `super' allows one to access
the overridden method instead of the overriding one. This message can only be
sent from "within" the object.
   `prolog' is a pseudo-object and refers to Prolog predicates. Thus,
`prolog::write(X)' is the same as the Prolog goal `write(X)'.

Note that a goal can be a (meta-) variable, which of course has to be
instantiated at run-time. The only overhead is that the goal is
translated to the internal message representation at run-time instead
of at compile-time. (There is still no inheritance search involved.)

Note that, if all subgoals of a goal term send messages to the same
object, this object can be extracted. For example, `O::A, O::B, O::C'
can be shortened to `O::(A,B,C)', or `findall(X,(::a(X);::b(X)),L)'
can be shortened to `::findall(X,(a(X);b(X)),L)'.
   However, this means that Prolog goals (defined in the resource
file; see the README file) are not available as object methods. (E.g.,
you cannot have methods `,/2' or `findall/3'.)

Terms in a method can be quoted with the prefix operator "`". Quoted terms
(goals or their arguments) are not translated by the compiler. For example,
while the pseudo-variable `this' will be replaced by the object's name,
"`this" represents the atom `this'.


5. Examples
-----------

In the following two sample objects, `permit/1' and `access/1' are public
methods in both `assistant' and `student', and `allowed/2' is a private method
in `assistant'. `access/1' defines the access rights of a subject depending on
its permit. Note that `student' overrides the definition of `permit/1', but
extends the definition of `access/1'.
   The answers to the goal `assistant::access(A)' are `A = office; A = lab';
the answers to the goal `student::access(A)' are `A = library; A = office'.
(See project program `demo/manual_pro'.)

   object assistant.                   object student is_a assistant.

      :- publish permit/1, access/1.      :- override permit/1, access/1.

      permit(total).                      permit(restricted).

      access(A) :-                        access(library).
         self::permit(P),                 access(A) :-
         this::allowed(P, A).                super::access(A).

      allowed(_, office).              end_object student.
      allowed(total, lab).

   end_object assistant.

Note that during the execution of a goal `assistant::access(A)', `self' refers
to `assistant', while during the execution of a goal `student::access(A)',
`self' refers to `student' (as this is the context in which `access/1' in
`assistant' will be evaluated).
   Note also the distinction of `self' and `this': `access/1' in `assistant'
can be inherited by subobjects, and `permit/1' may be overridden in those
objects (as in `student'), while `allowed/2' is a private method; therefore,
`access/1' sends message `permit(P)' to the current context, while it can send
message `allowed(P,A)' to object `assistant'.
   Finally note that `super' in `access/1' of `student' refers to `assistant',
as this is the object from which `student' has inherited the (overridden)
method `access/1'.

Inheritance using the first-found, most specialized method is shown in the
following example (also due to T. Sj\"oland). Note that object `d' inherits
`m/0' and `o/0' from `b' and `n/0' from `c'. The order of the superobjects
of `d' influences only the choice for `o/0'. (See project program
`demo/manual_pro'.)

   object a.                 object b is_a a.          object c is_a a.
      :- publish m/0, n/0.      :- publish o/0.           :- publish o/0.
                                :- override m/0.          :- override n/0.
      m. n.                     m. o.                     n. o.
   end_object a.             end_object b.             end_object c.

   object d is_a b, a, c.
   end_object d.


6. Instances
------------

As instances usually involves instance variables with state change and
thus destructive assignment, they are a difficult subject in Logic
Programming. It would be easy to add instance variables (subsequently
called attributes) by representing them as unary, dynamic facts, i.e.
attribute `A' of instance `I' with value `V' would be `A(V).' in `I'.
Changing the value means retracting the fact and asserting a new one.
The main contribution of OL(P) would be a mechanism to create and
delete instances, and some syntactic sugar for accessing and changing
values.

Although I have implemented a similar scheme in an earlier language, I
refrained from adding instances with state change to OL(P). Instead, I
tried to stick to the paradigm that OL(P) allows programs to have a
declarative semantics, and that OL(P) programs can be compiled to
efficient Prolog code. (Maybe you have comments or better ideas.)
   I focus on two useful elements of instances: attributes are
encapsulated, and they have to be stated only where needed. That is,
the attributes are collected in one place (the instance), and methods
that don't use some attributes don't have to carry them as arguments
(for example).

Therefore, instances are availabe in OL(P) in the following way.
   An instance `I' of object `Object' is referred to as `Object(I)';
`I' is the term containing the attribute values. A new instance is
created by `Object::new(I)', or - if some attributes `Ai' have to be
preset to values `Vi' - by `Object::new([Ai=Vi,...], I)'.
   Here, `I' is initialized to an instance with default attribute
values. Default attribute values are set in an object's begin
statement by adding a list of attribute assignment as single argument
to the object name, as in `object Object([Ai=Vi,...]).'. (In general,
the list elements can be both attributes and attributes assignments,
e.g., `object square([id,x=0,y=0]) is_a rectangle.' Attributes are not
restricted to atoms.)
   Default attribute values are inherited and overridden according to
the same scheme as methods. No `publish' or `override' declarations
are necessary, though.  If an attribute is listed without assignment,
its inherited value is not overridden.

A message `M' is sent to instance `I' of object `O' by `O(I)::M'. `I'
should have been generated as explained above. However, if default
attribute values are irrelevant (or don't exist), `I' can just be a
variable. Note that `O::M' is equivalent to `O(_)::M'.

The value `V' of attribute `A' of instance `I' is retrieved or set by
`I.A = V'. An attribute of the context can be accessed by `.A = V'.
Note that lists on the left-hand side of the `=' have to be quoted to be
distinguishable from this operation. This notation is available only
within objects. From Prolog, use `ol::val(I,A,V)', for example.
   There is no distinction between adding, retrieving, or checking an
attribute's value. For example, `I.a=1, I.a=V' first sets the value of
`a' and then retrieve it, and it is the same as `I.a=V, I.a=1'; also,
a subsequent `I.a=2' would fail.
   Also available are `I.A == V', which succeeds only if `A' has been
set and is identical to `V', and `I.A \== V', which succeeds in the
opposite case.
   Just like logic variables, attributes cannot be set to a
different value once instantiated (one could copy an instance and
replace attribute values, though), and an instantiation is
undone on backtracking (one could record the instance, though).

However, you can copy instances and replace attribute values in the
process. A new instance `I' is created from an instance `J' by `I :=
J.[Ai=Vi,...]'; `I' is equal to `J', except for attributes `Ai', which
are set to values `Vi' in `I'. A new instance can also be created from
the context by `I := .[Ai=Vi,...]'. (Note that `I := .[]' instantiates
`I' with the context instance; `I := J.[]' is equivalent to `I = J'.)
   You can of course assert and later retrieve instances, using the
Prolog database.
   A typical example (a new instance of `rect' would be generated by
`rect::new([w=20,h=50],I)', for example):

   object rect([id,x=0,y=0,w=0,h=0]).
      :- publish area/1, move/3.
      area(A) :-
         .w = W, .h = H, A is W*H.
      move(DX, DY, I) :-
         .x = X, .y = Y, NX is X+DX, NY is Y+DY,
         .id = Id, moveto(Id, NX, NY),
         I := .[x=NX,y=NY].
   end_object rect.

If a method changes attributes, it has to carry an extra parameter to
return the new instance. From the outside, this could be used as in
`rect(I)::area(A), rect(I)::move(5,5,J)'. There are alternatives
(e.g., an instance pair in each goal as in `rect(I,J)::area(A),
rect(J,K)::move(5,5)'), but they don't seem more attractive.

A word about the implementation: An instance `I' of object `O' (used
as in `O(I)') is represented by an extensible attribute-value
list `[Ai=Vi,...|_]' (e.g., `[x=100,y=150|_]').
   `I.A = V' is implemented by the usual "mem" operation `val(I,A,V)'
(see below), i.e. by looking for `A' in `I', unifying `V' with its
value if it exists, and adding `A=V' at the end if `A' doesn't exist.
(`.A = V' is equivalent to `val(CI,A,V)', where `CI' is the context
instance.) `I.A == V' is equivalent to `I.A = W, W == V', `I.A \== V'
to `I.A = W, W \== V'.

   % val(I, A, V)
   val([A=W|_], A, V) :- !, W = V.
   val([_|As], A, V)  :- val(As, A, V).

At compile time, a default instance (attribute-value list) `DI' with
variable tail is generated for each object.
   `O::new(I)' is equivalent to `I = DI', and `O::new([Ai=Vi,...], I)'
is equivalent to `I = [Ai=Vi,...|DI]'.
   `I := J.[Ai=Vi,...]' is equivalent to `I = [Ai=Vi,...|J]';
`I := .[Ai=Vi,...]' is equivalent to `I = [Ai=Vi,...|CI]', where `CI'
is the context instance.

An important point of this representation is that attribute-value
lists have proved to be easy and efficient to use in Prolog, they
don't rely on mechanisms provided by OL(P) specifically, and they can
be used in Prolog without knowing anything about compiler choices
(since there are none).
   In the applications I am currently focussed on, attributes are
typically set in a first pass and then collectively processed, which
can be done much more efficiently by processing the list directly as
opposed to using OL(P) mechanisms (e.g., going through the list
recursively instead of doing `I.x = X, I.y = Y, ...'). Thus, OL(P) can
be used effectively for modeling purposes, while Prolog is used for
standard high-volume processing tasks.


7. OL(P) in SICStus and QUINTUS Prolog
--------------------------------------

The README file explains how to install OL(P). Then, to start Prolog
together with the OL(P) system, use the script `bin/ol', i.e. enter

   Path/ol

in a shell, where `Path/' is the path to the OL(P) `bin/' directory
(could be empty, depending on how you set up the OL(P) directory and
your shell environment). To load the OL(P) system in an already
running Prolog process, call "restore('bin/ol')", or consult the file
`bin/ol.pl'.

Once compiled OL(P) programs are loaded, they can be called from
Prolog using `::/2'. Similar expansion as in OL(P) is available. The
context object initially is `prolog'.

Note that the library `gmlib' is not available for QUINTUS Prolog, and
that the examples using this library will not run for QUINTUS Prolog.


8. Projects
-----------

OL(P) project management is done online in Prolog system.
   The built-in object `project' allows you to create, configure, and compile
projects and their files. (See the Reference-Manual for the full
documentation.) By default, everything is allocated in the module `user'.
   Method `create/2' defines a new project and its files. (Only one project can
be defined at a time.) Methods 'add_files/1' and `add_library/1' add files and
libraries to the project; files and libraries can also be removed. Libraries
are simply other projects; they in turn consist of files and may even contain
other libraries, and so on. Method `switch/1' switches to another, existing
project.
   Once a project is created and its files are written, methods `compile/.'
compile individual files or an entire project. (Only the main project's files
can be compiled; libraries have to be compiled as their own projects.) This is
an incremental compilation, i.e. individual files can be recompiled after a
change. It is also a file-to-file compilation; files are compiled to the base
language and may subsequently be compiled by the compiler of that language
before they are loaded.
   With each project manipulation, the data of the object layer is written to
the project file `Project.pl'. (In a later version, this file, the compiled
program files, the project and program files of the add libraries, and the run-
time kernel and built-in objects of OL(P) together form the Prolog program that
is independent of the OL(P) system.)

For example, imagine a project `oodb' with files for the database, the query
user interface, and an inference engine, written in OL(P) based on Prolog. The
message

   | ?- project::create(oodb, ['database.pl','ui.pl','inference.pl']).

creates the project. Note that in this case the OL(P) files are
`database.pl.ol', `ui.pl.ol', and `inference.pl.ol'. Once the files exist (i.e.
you have written your the programs), the message

   | ?- project::compile.

compiles all files of the project. If one file, say `ui.pl.ol', has been
changed, the message

   | ?- project::compile(['ui.pl']).

recompiles just that file.

Message

   | ?- project::do(consult(_)).

consults the files of the project and its added libraries. (This is plain
consulting of the `.pl' files; no additional translation is required, and
individual files can simply be consulted with Prolog's `consult'. Alternatively
for Prolog files, `fcompile/.' and `load/.' are available. For non-Prolog
programs, more sophisticated versions of method `do/.' are available.)
   Then, a method of one of the project's objects, say `retrieve/2' of `query',
can be called by

   | ?- query::retrieve(smith, salary, S).

You can leave the Prolog system anytime and later reload the current project
state with the message

   | ?- project::switch(oodb).


9. Meta-Programs for OL(P) Programs
-----------------------------------

Meta-programs (meta-interpreters, partial evaluators, declarative debuggers
etc.) for OL(P) programs are written just as for programs in the base language.
In addition, the interface clauses introduced and clause heads produced by the
compiler can be interpreted to get additional information about the original
object structure of the program. (This is only possible as long as the program
is not optimized.) The structure of a compiled, non-optimized OL(P) is as
follows. (The library object `meta_object' provides methods that abstract from
the particular representation. A sample program using this information is the
tracer defined in object `listener'.)

A method clause `H:-B' defined in object `O' is compiled to the clause
`O(CH,Self,I):-CB', where `CH' and `CB' are the compiled versions of `H' and
`B', and `I' is a variable for instance attributes.
Basically, object goals `R(I)::M' in object `O' are expanded to goals
`ol_send(M,rsd(R,O,Def,I))', where `Def' is `inh' if `R' is `super', and `loc'
otherwise (see below). Also, pseudo-variables are replaced. `R::M' is the same
as `R(_)::M'.

For each method `M' accessible in an object `O', an interface clause

   ol_send(M, rsd(O,PO,Def,I)) :- H(M, O, I).

is added to the compiled program. `H' is the handler object for `M', i.e. the
object that really defines `M' and from which `O' has inherited it. `PO' is a
variable if `M' is public, and equals `O' if it is private. (Thus, the goal
`R::M' in object `O' only succeeds if `R' and `O' are equal, i.e. if the
message is send from within the object.) `Def' is either `loc' (for local or
non-overridden, inherited methods), or `inh' (for inherited and overridden
methods). Notice that `O' in the body of the interface clause becomes the
method's `Self' variable. `I' again holds the instance attributes.

When writing a meta-program for compiled OL(P), you can simply ignore the
special goals, since the compiled code is plain Prolog (if Prolog is the base
language). However, you can also choose to make use of the object structure to
partition the information used by the program as well as the information
presented to the user. For example, a partial evaluator may unfold predicates
in certain objects only, or a declarative debugger may declare entire objects
as reliable and ask the user questions about predicates with respect to objects
(e.g., "Is `access(library)' true in object `student'?"). In many cases, this
greatly increases granularity and improves orientation.

(The `listener' in `demo/listener.pl.ol' is a sample meta-interpreter which
interprets compiled OL(P) programs - i.e. Prolog programs -, but which
presents a trace in object terms. See project program `demo/manual_pro'.
Note that objects which are accessed in meta-programming, e.g., with
`clause/2', have to be declared dynamic.)

By the way, the interface clauses `ol_send/2' inflate the program somewhat.
This will improve with optimization, where the program will be downsized
considerably.


10. Operator Declarations in the OL(P) System
--------------------------------------------

object      fx   1002
end_object  fx   1002
publish     fx   1002
override    fx   1002
unfold      fx   1002
is_a       xfy   1001
in         xfy    999
:=         xfx    700
::         xfy    550
::          fy    550
`           fx    650
.          xfy    180
.           fy    180
