Declaring classes and interfaces

Author(s): Angel Fernandez Pineda.

O'Ciao classes are declared in the same way as traditional prolog modules. The general mechanism of source expansion will translate object-oriented declarations to normal prolog code. This is done transparently to the user.

Abstract interfaces are restricted classes which declare exported predicates with no implementation. The implementation itselt will be provided by some class using an implements/1 declaration. Only export/1 and data/1 declarations are allowed when declaring an interface. Normal classes may treated as interfaces just ignoring all exported predicate implementations.

Usage and interface

  • Library usage:
    To declare a class the compiler must be told to use the class source expansion. To do so, source code must start with a module declaration which loads the class package:
               :- class(ClassName).
    
    or a module/3 declaration, as follows:
               :- module(ClassName,[],[class]).
    

    interfaces are declared in a similar way:

               :- interface(InterfaceName).
    

    Please, do not use SICStus-like module declaration, with a non-empty export list. In other case, some non-sense errors will be reported by normal Ciao module system.

    Most of the regular Ciao declarations may be used when defining a class, such as concurrent/1,dynamic/1, discontiguous/1,multifile/1, and so on.

    However, there are some restrictions wich apply to those declarations:

    • meta_predicate/1 declaration is not allowed to hold addmodule and pred(N) meta-arguments, except for previously declared multifiles.
    • Attribute and multifile predicates must be declared before any clause of the related predicate.
    • There is no sense in declaring an attribute as meta_predicate.

    It is a good practique to put all your declarations at the very begining of the file, just before the code itself.

  • Exports:
  • New declarations defined:
    export/1, public/1, inheritable/1, data/1, dynamic/1, concurrent/1, inherit_class/1, implements/1, virtual/1.
  • Other modules used:

Documentation on new declarations

DECLARATION
Declares a method or attribute to be part of the public interface.

The public interface is the set of predicates wich will be accesible from any code establishing an usage relationship with this class (see use_class/1 for further information).

Publishing an attribute or method is very similar to exporting a predicate in a Prolog module.

Whether an inherited and exported predicate is overriden, it must be explicitly exported again.

An inherited (but not exported) predicate may become exported, without overriding it by the usage of this declaration.

Usage: :- export(Spec).

  • Description: Spec will be part of the public (exported) interface.
  • The following properties should hold at call time:
    (objects_rt:method_spec/1)Spec is a method or attribute specification.

DECLARATION
Just an alias for export/1.

Usage: :- public(Spec).

  • Description: This declaration may be used instead of export/1.
  • The following properties should hold at call time:
    (objects_rt:method_spec/1)Spec is a method or attribute specification.

DECLARATION
Declares a method or attribute to be inherited by descendant classes. Notice that all public predicates are inheritable by default. There is no need to mark them as inheritable.

Traditionaly, object oriented languages makes use of the protected concept. Inheritable/1 may be used as the same concept.

The set of inheritable predicates is called the inheritable interface.

Usage: :- inheritable(MethodSpec).

  • Description: MethodSpec is accessible to descendant classes.
  • The following properties should hold at call time:
    (objects_rt:method_spec/1)MethodSpec is a method or attribute specification.

DECLARATION
Declares an attribute at current class. Attributes are used to build the internal state of instances. So, each instance will own a particular copy of those attribute definitions. In this way, one instance may have different state from another.

O'Ciao attributes are restricted to hold simple facts. It is not possible to hold a Head :- Body clause at an instance attribute.

Notice that attributes are multi-evaluated by nature, and may be manipulated by the habitual assert/retract family of predicates.

Attributes may also be initialized. In order to do so, simply put some clauses after the attribute definition. Each time an instance is created, its initial state will be built from those initialization clauses.

Note: whether a data/1 declaration appears inside an interface, it will be automatically exported.

Usage: :- data Spec.

  • Description: Spec is an attribute.
  • The following properties should hold at call time:
    (objects_rt:method_spec/1)Spec is a method or attribute specification.

DECLARATION
Just an alias for data/1.

Usage: :- dynamic Spec.

  • Description: You may use this declaration instead of data/1.
  • The following properties should hold at call time:
    (objects_rt:method_spec/1)Spec is a method or attribute specification.

DECLARATION
Declares a concurrent attribute at current class. Concurrent attributes are just the same as normal attributes, those declared using data/1, except for they may freeze the calling thread instead of failing when no more choice points are remaining on the concurrent attribute.

In order to get more information about concurrent behavior take a look to the concurrent/1 built-in declaration on Ciao Prolog module system.

Usage: :- concurrent Spec.

  • Description: Declares Spec to be a concurrent attribute.
  • The following properties should hold at call time:
    (objects_rt:method_spec/1)Spec is a method or attribute specification.

DECLARATION
Makes any public and/or inheritable predicate at inherited class to become accesible by any instance derived from current class.

Inherited class is also called the super class.

Only one inherit_class/1 declaration is allowed to be present at current source.

Notice that inheritance is public by default. Any public and/or inheritable declaration will remain the same to descendant classes. However, any inherited predicate may be overriden (redefined).

A predicate is said to be overriden when it has been inherited from super class, but there are clauses (or a data/1 declaration) present at current class for such a predicate.

Whether a public predicate is overriden, the local definition must also be exported, otherwise an error is reported.

Whether an inheritable predicate (not public) is overriden, the local definition must also be marked as inheritable or exported, otherwise an error is also reported.

Note: whether inherit_class/1 appears inside an interface, it will be used as an implements/1 declaration.

Usage: :- inherit_class(Source).

  • Description: Establish an inheritance relationship between current class and the class defined at Source file.
  • The following properties should hold at call time:
    (objects_rt:class_source/1)Source is a valid path to a prolog file containing a class declaration (without .pl extension).

DECLARATION
Forces current source to provide an implementation for the given interface file. Such interface file may declare another class or a specific interface.

Every public predicate present at given interface file will be automatically declared as public at current source, so you must provide an implementation for such predicates.

The effect of this declaration is called interface inheritance,and there is no restriction on the number of implements/1 declarations present at current code.

Usage: :- implements(Interface).

  • Description: Current source is supposed to provide an implementation for Interface.
  • The following properties should hold at call time:
    (objects_rt:interface_source/1)Interface is a valid path to a prolog file containing a class declaration or an interface declaration (without .pl extension).

DECLARATION
This declaration may be used whenever descendant classes are to implement different versions of a given predicate.

virtual predicates give a chance to handle, in an uniform way, different implementations of the same functionality.

Whether a virtual predicate is declared as a method, there must be at least one clause of it present at current source. Whenever no special implementation is needed at current class, a never-fail/allways-fail clause may be defined (depending on your needs). For example:

   :- virtual([ test1/1 , test2/2 ]).
   test1(_).
   test2(_,_) :- fail.

This kind of virtual methods are also known as abstract methods, since implementation is fully delegated to descendant classes.

An attribute may be also declared as a virtual one, but there is no need to write clauses for it.

Usage: :- virtual(VirtualMethodSpec).

  • Description: All calls to VirtualMethodSpec predicate in current source will use the most descendant implementation of it.
  • The following properties should hold at call time:
    (objects_rt:virtual_method_spec/1)VirtualMethodSpec is a method specification.

Documentation on exports

PREDICATE
This predicate qualificator may be used whenever you need to reference an attribute or method on the super class.

Since methods and attributes may be redefined, this qualificator is need to distinguish between a locally declared predicate and the inherited one, which has the same name.

There is no need to use inherited/1 if a particular inherited predicate has not been redefined at current class.

Usage: inherited(Goal)

  • Description: References a given Goal at the super class
  • The following properties should hold at call time:
    (basic_props:callable/1)Goal is a term which represents a goal, i.e., an atom or a structure.

PREDICATE
Determines which instance is currently executing self/1 goal.

Predicate will fail if argument is not a free variable. Otherwise, it will allways succeed, retrieving the instance identifier which is executing current code.

This functionality is very usefull since an object must have knowledge of other object's identifier in order to send messages to it.For example:

:- concurrent ack/0.

send_data_to_object(Data,Obj) :- self(X), Obj:take_this(Data,X), current_fact(ack).

acknowledge :- asserta_fact(ack).

take_this(Data,Sender) :- validate_data(Data), Sender:acknowledge.

Usage: self(Variable)

  • Description: Retrieves current instance identifier in Variable
  • The following properties should hold at call time:
    (term_typing:var/1)Variable is a free variable.
  • The following properties should hold upon exit:
    (objects_rt:instance_id/1)Variable is an unique term which identifies an object.

PREDICATE
A constructor is a special case of method which complains the following conditions:

  • The constructor functor matches the current class name.
  • A constructor may hold any number of arguments.
  • If an inheritance relationship was defined, an inherited constructor must be manually called (see below).
  • When instance creation takes place, any of the declared constructors are implicitly called. The actual constructor called depends on the new/2 goal specified by the user.

This is a simple example of constructor declaration for the foo class:

           foo :- 
               display('an instance was born').

Constructor declaration is not mandatory, and there may be more than one constructor declarations (with different arity) at the source code.

This functionality is usefull when some computation is needed at instance creation. For example: opening a socket, clearing the screen, etc.

Whenever an inheritance relationship is established, and there is any constructor defined at the super class, you must call manually an inherited constructor. Here is an example:

           :- class(foo).
           :- inherit_class(myclass).

           foo :-
               myclass(0),
               display('an instance was born').

           foo(N) :- myclass(N).

Consequences may be unpredictable, if you forget to call an inherited constructor. You should also take care not to call an inherited constructor twice.

All defined constructors are inheritable by default. A constructor may also be declared as public (by the user), but it is not mandatory.

Usage:

  • Description: Constructors are implicitly declared

PREDICATE
A destructor is a special case of method which will be automatically called when instance destruction takes place.

A destructor will never be wanted to be part of the public interface, and there is no need to mark them as inheritable, since all inherited destructors are called by O'Ciao just before yours.

This is a simple example of destructor declaration:

           destructor :- 
               display('goodbye, cruel world!!!').

Destructor declaration is not mandatory. Failure or sucess of destructors will be ignored by O'Ciao, and they will be called only once.

This functionality is useful when some computation is need at instance destruction. For example: closing an open file.

Usage:

  • Description: Destructors are implicitly declared

Other information

This describes the errors reported when declaring a class or an interface. The first section will explain compile-time errors, this is, any semantic error which may be determined at compile time. The second section will explain run-time errors, this is, any exception that may be raisen by the incorrect usage of O'Ciao. Some of those errors may be not reported at compile time, due to the use of meta-programational structures. For example:

functor(X,my_method,0),call(X).

O'Ciao is not able to check whether my_method/0 is a valid method or not. So, this kind of checking is left to run time.

Class and Interface error reporting at compile time

  • ERROR : multiple inheritance not allowed.

    There are two or more inherit_class/1 declarations found at your code. Only one declaration is allowed, since there is no multiple code inheritance support.

  • ERROR : invalid inheritance declaration.

    The given parameter to inherit_class/1 declaration is not a valid path to a Prolog source.

  • ERROR : sorry, addmodule meta-arg is not allowed at F/A.

    You are trying to declare F/A as meta-predicate, and one of the meta-arguments is addmodule. This is not allowed in O'Ciao due to implementation restrictions. For example:

    :- meta_predicate example(addmodule).

    example(X,FromModule) :- call(FromModule:X).

  • ERROR : invalid attribute declaration for Arg.

    Argument to data/1 or dynamic/1 declaration is not a valid predicate specification of the form Functor/Arity. For example:

    :- data attr.

    :- dynamic attr(_).

    :- data attr/m.

    etc,etc...

  • ERROR : pretended attribute F/A was assumed to be a method.

    You put some clauses of F/A before the corresponding data/1 or dynamic/1 declaration. For example:

    attr(initial_value).

    :- data attr/1.

    It is a must to declare attributes before any clause of the given predicate.

  • ERROR : destructor/0 is not allowed to be an attribute.

    There is a :- data(destructor/0) or :- dynamic(destructor/0). declaration in your code. This is not allowed since destructor/0 is a reserved predicate, and must be allways a method.

  • ERROR : Constructor is not allowed to be an attribute.

    As the previos error, you are trying to declare a constructor as an attribute. A constructor must be allways a method.

  • ERROR : invalid multifile: destructor/0 is a reserved predicate.

    There is a :- multifile(destructor/0). declaration in your code. This is not allowed since destructor/0 is a reserved predicate, and must be allways a method.

  • ERROR : invalid multifile: Constructor is a reserved predicate.

    As the previos error, you are trying to declare a constructor as a multifile. Any constructor must allways be a method.

  • ERROR : multifile declaration of F/A ignored: it was assumed to be a method.

    You put some clauses of F/A before the corresponding multifile/1 declaration. For example:

    example(a,b).

    :- multifile example/2.

    Multifile predicates must be declared before any clause of the given predicate.

  • ERROR : invalid multifile declaration: multifile(Arg).

    Given argument to multifile/1 declaration is not a valid predicate specification, of the form Functor/Arity.

  • ERROR : invalid public declaration: Arg.

    Given argument Arg to public/1 or export/1 declaration is not a valid predicate specification, of the form Functor/Arity.

  • ERROR : invalid inheritable declaration: inheritable(Arg).

    Given argument Arg to inheritable/1 declaration is not a valid predicate specification, of the form Functor/Arity.

  • ERROR : destructor/0 is not allowed to be virtual.

    There is a :- virtual(destructor/0) declaration present at your code. Destructors and/or constructors are not allowed to be virtual.

  • ERROR : Constructor is not allowed to be virtual.

    As the previous error, you are trying to declare a constructor as virtual. This is not allowed.

  • ERROR : invalid virtual declaration: virtual(Arg).

    Given argument to virtual/1 declaration is not a valid predicate specification, of the form Functor/Arity.

  • ERROR : clause of F/A ignored : only facts are allowed as initial state.

    You declared F/A as an attribute, then you put some clauses of that predicate in the form Head :- Body. For example:

    :- data my_attribute/1.

    my_attribute(X) :- X>=0 , X<=2.

    This is not allowed since attributes are assumed to hold simple facts. The correct usage for those initialization clauses is:

    :- data my_attribute/1.

    my_attribute(0).

    my_attribute(1).

    my_attribute(2).

  • ERROR : multifile F/A is not allowed to be public.

    The given F/A predicate is both present at multifile/1 and public/1 declarations. For example:

    :- public(p/1).

    :- multifile(p/1).

    This is not allowed since multifile predicates are not related to Object Oriented Programming.

  • ERROR : multifile F/A is not allowed to be inheritable.

    Analogous to previous error.

  • ERROR : multifile F/A is not allowed to be virtual.

    Analogous to previous error.

  • ERROR : virtual F/A must be a method or attribute defined at this class.

    There is a virtual/1 declaration for F/A, but there is not any clause of that predicate nor a data/1 declaration. You must declare at least one clause for every virtual method. Virtual attributes does not require any clause but a data/1 declaration must be present.

  • ERROR : implemented interface Module is not a valid interface.

    There is an implements/1 declaration present at your code where given Module is not declared as class nor interface.

  • ERROR : predicate F/A is required both as method (at Itf1 interface) and attribute (at Itf2 interface).

    There is no chance to give a correct implementation for F/A predicate since Itf1 and Itf2 interfaces require different definitions. To avoid this error, you must remove one of the related implements/1 declaration.

  • ERROR : inherited Source must be a class.

    There is an :- inherit_class(Source) declaration, but that source was not declared as a class.

  • ERROR : circular inheritance: Source is not a valid super-class.

    Establishing an inheritance relationship with Source will cause current class to be present twice in the inheritance line. This is not allowed. The cause of this is error is simple : There is some inherited class from Source which also establishes an inheritance relationship with current source.

  • ERROR : method/attribute F/A must be implemented.

    Some of the implemented interfaces requires F/A to be defined, but there is no definition for such predicate, even an inherited one.

  • ERROR : local implementation of F/A hides inheritable/public definition.

    There is an inherited definition for F/A which is been redefined at current class, but there is no valid inheritable/public declaration for the last one. Overriden public predicates must be also declared as public. Overriden inheritable predicates must be declared either as public or inheritable.

  • ERROR : public predicate F/A was not defined nor inherited.

    There is a public/1 declaration for F/A, but there is no definition for it at current class nor an inherited one.

  • ERROR : argument to self/1 must be a free variable.

    Argument to self/1 is not a variable, for example: self(abc).

  • ERROR : unknown inherited attribute in Goal.

    Goal belongs to assert/retract family of predicates, and given argument is not a valid inherited attribute. The most probable causes of this error are:

    • The given predicate is defined at super-class, but you forgot to mark it as inheritable (or public), at such class.
    • The given predicate was not defined (at super-class) as an attribute, just as a method.

  • ERROR : unknown inherited goal: Goal.

    The given Goal was not found at super-class, or it is not accessible. Check whether Goal was marked as inheritable (or public) at super-class.

  • ERROR : invalid argument: F/A is not an attribute.

    You are trying to pass a method as an argument to any predicate which expect a fact predicate.

  • ERROR : unknown inherited fact: Fact.

    There is a call to any predicate which expects a fact argument (those declared as data or dynamic),but the actual argument is not an inherited attribute.For example:

    asserta_fact(inherited(not_an_attribute(8)))

    where not_an_attribute/1 was not declared as data or dynamic by the super-class (or corresponding ascendant).

  • ERROR : unknown inherited spec: F/A.

    There is a reference to an inherited predicate specification, but the involved predicate has not been inherited.

  • WARNING : meta-predicate specification of F/A ignored since this is an attribute.

    You declared F/A both as an attribute and a meta-predicate. For example:

    :- meta_predicate attr(goal).

    :- data attr/1.

    There is no sense in declaring an attribute as meta-predicate.

  • WARNING : class destructor is public

    There is a :- public(destructor/0) declaration present at your code. Marking a destructor as public is a very bad idea since anybody may destroy or corrupt an instance before the proper time.

  • WARNING : class destructor is inheritable

    Analogous to previous error.

  • WARNING : There is no call to inherited constructor/s

    You have not declared any constructor at your class, but there is any inherited constructor that should be called. Whenever you do not need constructors, but there is an inheritance relationship (where super-class declares a constructor), you should write a simple constructor as the following example:

       :- class(myclass).
       :- inherit_class(other_class).
    
       myclass :-
               other_class.
    

  • WARNING : multifile F/A hides inherited predicate.

    You declared as multifle a predicate which matches an inherited predicate name. Any reference to the inherited predicate must be done by the ways of the inherited/1 qualificator.

Class and Interface error reporting at run time

  • EXCEPTION : error(existence_error(object_goal,Goal),Mod).

    Called Goal from module (or class) Mod is unknown or has not been published.

Normal Prolog module system interaction

O'Ciao works in conjunction with the Ciao Prolog module system, which also reports its own error messages. This will cause Ciao to report a little criptic error messages due to the general mechanism of source-to-source expansion. Those are some tips you must consider when compiling a class:

  • Any error relative to method 'm' with arity A will be reported for predicate 'obj$m'/A+1. For example :

    WARNING: (lns 28-30) [Item,Itema] - singleton variables in obj$remove/2

    This error is relative to method remove/1.

  • set_prolog_flag/1 declaration will be usefull when declaring multiple constructors. It will avoid some awful warnings. Example:

       :- class(myclass).
    
       %% Use this declaration whenever several constructors are needed.
    
       :- set_prolog_flag(multi_arity_warnings,off).
    
       myclass(_).
    
       myclass(_,_).
    
       :- set_prolog_flag(multi_arity_warnings,on).
    

Known bugs and planned improvements

  • addmodule and pred(N) meta-arguments are not allowed on meta-predicates.