4.5 Behavioural Integration
4.5.1 From the Logic to the Object-Oriented Language
This section discusses the behavioural integration from the logic perspective. A high level view of the mapping from predicates to methods is shown in figure 4.2. The details of this mapping are explained throughout the development of this section.
module:predicate(argp1, argp2,… argpw)
object.method(argm1, argm2,… argmv)
unifies
returns
(argp1*, argp2*,… argpw*)
an object
side effects
}
(argm1*, argm2*,… argmv*)
Inferring the Receiver of the Method
In most object-oriented languages there is typically a receiver for each method invocation. In certain cases, the receiver could be an artefact representing a class of objects. Depending on the language, such class may just be a plain object (e.g., Smalltalk [61] and Ruby [57]).
Although in logic programming predicates may not necessarily be encapsu- lated into an entity, often logic languages do provide the notion of a module as an encapsulation unit. Alternatively, object-oriented extensions could provide the notion of an object into the logic programming environment [96, 53, 146, 113].
By assuming one of these encapsulation units, the problem of inferring the receiver of a method is reduced to the problem of mapping a module to such a receiver.
Formally, we define a mapping function · from modules to objects as:
· : Smo æ OB : ·(mo) = ob, where OB = {ob | ob – C · C œ Sc}
which maps a module mo œ MO to a receiver ob œ OB on the object-oriented side (i.e., an instance of a symbiotic class).
Inferring the Name of the Method
A straightforward solution to the problem of inferring a method from a pred- icate name is to assume they both have the same name. However, a problem with this mapping strategy is the different kind of naming conventions that may exist in the two languages. For example, several object-oriented languages (e.g., Java and Smalltalk) follow a camel-case convention for naming meth- ods (e.g., ‘myMethod’). Conversely, logic languages (like Prolog) typically name predicates by separating tokens in a name using an underscore (e.g., ‘my_predicate’).
Therefore, introducing method names following a convention distinct to the one expected may limit the transparency of the integration, since the pro- grammer may be forced to work with conventions belonging to two different languages in the same unit.
Note that naming conventions are not just a matter of aesthetics. Certain frameworks and external libraries may depend on the respect of such conven- tions in order to function as expected (e.g., frameworks based on the Jav- aBeans specification [106] or Ruby on Rails’ convention over configuration principle [134]).
We believe that it should be up to the programmer to decide on the best policy for mapping routine names between the two worlds. This mapping can be specified by means of a function ‚ : Np æ Nm
which maps the name Np of a predicate p œ Sp to the name of its corresponding
Transforming Predicate Parameters to Method Parameters
A mechanism to map predicate parameters (i.e., logic terms) to method pa- rameters should also be specified. Particularly, the types of the parameters in the method should not be limited to being a reification of a logic type (i.e., instances of a class reifying a Term class). Instead, any valid type should be acceptable.
To achieve this ’p œ Sp, a programmer should be able to specify a function
„ such that „(argp1, argp2, ...argpw) = (argm1, argm2, ...argmv)
Where w is the number of parameters of p and v the number of parameters of the corresponding method. argpi is a logic term for each 1 <= i <= w and
argmj is an object for each 1 <= j <= v.
Note that the arity of the predicate parameter list may be different from the one of the method parameters.
In addition, not all predicate arguments may be bound, which brings us to the next integration problem.
Interpreting Unbound Logic Variables
Predicate parameters could include unbound logic variables. Therefore, a se- mantics for interpreting those variables on the object-oriented side is required. Two scenarios are possible. The first corresponds to mapping a logic variable to a method parameter reifying a logic variable (e.g., an instance of a Variable class). In this case, the method should be able to bind a value to the variable, causing a side effect on the logic side. Note that a same variable may appear more than once. If that occurs, binding one variable to a value should affect all of its occurrences. Although this approach allows side effects on the logic side, a disadvantage is that the method on the object-oriented side could have to deal explicitly with the concept of a logic variable, thus making explicit the integration concern.
The other scenario corresponds to the variable being interpreted as an object not reifying a logic term. In that case, an alternative semantics should be defined. For example, assuming the value of the corresponding object as null or an equivalent.
Interpreting the Return Value of a Method
The presence of variables in the predicate parameters illustrated a scenario that may cause a side effect after delegating to the object-oriented side (i.e., the bind- ing of the unbound variables). However, the parameters of the method (derived from the predicate parameters) are not the only ones that may determine the new state of the predicate parameters on the logic side. The return value of the method, if present, may also influence the new state of the parameters of the logic predicate.
Therefore, ’p œ Sp a programmer should be able to define a mapping deter-
Ï(argm1ú, argm2ú, ...argmvú), r) = (argp1ú, argp2ú, ...argpwú)
Where w is the number of parameters of p and v the number of parameters of the method. argmiú is the final state of a method parameter for each 1 <= i <= v,
r is the method return value and argpjú is the new state of a predicate parameter
for each 1 <= j <= w.