Logtalk user manual
Message sending

Message sending

Messages allows us to call object predicates. Logtalk uses the same nomenclature found in other object-oriented programming languages such as Smalltalk. Therefore, the terms predicate and method are often used interchangeably when referring to predicates defined inside objects and categories. A message must always match a predicate within the scope of the sender object.

Note that message sending is only the same as calling an object's predicate if the object does not inherit (or import) predicate definitions from other objects (or categories). Otherwise, the predicate definition that will be executed may depend on the relationships of the object with other Logtalk entities.

Operators used in message sending

Logtalk declares the following operators for the message sending control constructs:

:- op(600, xfy, ::).
:- op(600,  fy, ::).
:- op(600,  fy, ^^).

It is assumed that these operators remain active (once the Logtalk compiler and runtime files are loaded) until the end of the Prolog session (this is the usual behavior of most Prolog compilers). Note that these operator definitions are compatible with the pre-defined operators in the Prolog ISO standard.

Sending a message to an object

Sending a message to an object is accomplished by using the ::/2 control construct:

| ?- Object::Message.

The message must match a public predicate declared for the receiving object. The message may also correspond to a protected or private predicate if the sender matches the predicate scope container. If the predicate is declared but not defined, the message simply fails (as per the closed-world assumption).

Delegating a message to an object

It is also possible to send a message to an object while preserving the original sender by using the []/1 delegation control construct:

..., [Object::Message], ....

This control construct can only be used within objects and categories (at the interpreter top-level, the sender is always the pseudo-object user so using this control construct would be equivalent to use the ::/2 message sending control construct).

Sending a message to self

While defining a predicate, we sometimes need to send a message to self, i.e., to the same object that has received the original message. This is done in Logtalk through the ::/1 control construct:

::Message

The message must match either a public or protected predicate declared for the receiving object or a private predicate within the scope of the sender otherwise an error will be thrown (see the Reference Manual for details). If the message is sent from inside a category or if we are using private inheritance, then the message may also match a private predicate. Again, if the predicate is declared but not defined, the message simply fails (as per the closed-world assumption).

Broadcasting

In the Logtalk context, broadcasting is interpreted as the sending of several messages to the same object. This can be achieved by using the message sending method described above. However, for convenience, Logtalk implements an extended syntax for message sending that may improve program readability in some cases. This extended syntax uses the (,)/2, (;)/2, and (->)/2 control constructs. For example, if we wish to send several messages to the same object, we can write:

| ?- Object::(Message1, Message2, ...).

This is semantically equivalent to:

| ?- Object::Message1, Object::Message2, ... .

This extended syntax may also be used with the ::/1 message sending control construct.

Calling imported and inherited predicate definitions

When redefining a predicate, sometimes we need to call the inherited definition in the new code. This functionality, introduced by the Smalltalk language through the super primitive, is available in Logtalk using the ^^/1 control construct:

^^Predicate

Most of the time we will use this control construct by instantiating the pattern:

Predicate :-
    ...,            % do something
    ^^Predicate,    % call inherited definition
    ... .           % do something more

This control construct is generalized in Logtalk where it may be used to call any imported or inherited predicate definition. This control construct may be used within objects and categories. When combined with static binding, this control construct allows imported and inherited predicates to be called with the same performance of local predicates. As with the message sending control constructs, the ^^/1 call simply fails when the predicate is declared but not defined (as per the closed-world assumption).

Message sending and event generation

Every message sent using the ::/2 control construct generates two events, one before and one after the message execution. Messages that are sent using the ::/1 (message to self) control construct or the ^^/1 super mechanism described above do not generate any events. The rational behind this distinction is that messages to self and super calls are only used internally in the definition of methods or to execute additional messages with the same target object (represented by self). In other words, events are only generated when using an object's public interface; they cannot be used to break object encapsulation.

If we need to generate events for a public message sent to self, then we just need to write something like:

Predicate :-
    ...,
    self(Self),       % get self reference
    Self::Message,    % send a message to self using ::/2
    ... .

If we also need the sender of the message to be other than the object containing the predicate definition, we can write:

Predicate :-
    ...,
    self(Self),       % send a message to self using ::/2
    {Self::Message},  % sender will be the pseudo-object user
    ... .

When events are not used, is possible to turn off event generation on a per object basis by using the events/1 compiler flag. See the section on event-driven programming for more details.

Message sending performance

Logtalk supports both static binding and dynamic binding. Static binding is used whenever messages are sent (using ::/2) to static objects already loaded and with the optimize compiler flag turned on. When that is not the case (or when using ::/1), Logtalk uses dynamic binding coupled with a caching mechanism that avoids repeated lookups of predicate declarations and predicate definitions. This is a solution common to other programming languages supporting dynamic binding. Message lookups are automatically cached the first time a message is sent. Cache entries are automatically removed when loading entities or using Logtalk dynamic features that invalidate the cached lookups.

Whenever static binding is used, message sending performance is roughly the same as a predicate call in plain Prolog. When discussing Logtalk dynamic binding performance, two distinct cases should be considered: messages sent by the user from the top-level interpreter and messages sent from compiled objects. In addition, the message declaration and definition lookups may, or may not be already cached by the runtime engine. In what follows, we will assume that the message lookups are already cached.

Translating message processing to predicate calls

In order to better understand the performance tradeoffs of using Logtalk dynamic binding when compared to plain Prolog or to Prolog module systems, is useful to translate message processing in terms of predicate calls. However, in doing this, we should keep in mind that the number of predicate calls is not necessarily proportional to the time taken to execute them.

With event-support turned on, a message sent from a compiled object to another object translates to three predicate calls:

checking for before events
one call to the built-in predicate \+/1, assuming that no events are defined
method call using the cached lookup
one call to a dynamic predicate (the cache entry)
checking for after events
one call to the built-in predicate \+/1, assuming that no events are defined

Given that events can be dynamically defined at runtime, there is no room for reducing the number of predicate calls without turning off support for event-driven programming. When events are defined, the number of predicate calls grows proportional to the number of events and event handlers (monitors). Event-driven programming support can be switched off for specific object using the compiler flag events/1. Doing so, reduces the number of predicate calls from three to just one.

Messages to self and super calls are transparent regarding events and, as such, imply only one predicate call (to the cache entry, a dynamic predicate).

When a message is sent by the user from the top-level interpreter, Logtalk needs to perform a runtime translation of the message term in order to prove the corresponding goal. Thus, while sending a message from a compiled object corresponds to either three predicate calls (event-support on) or one predicate call (event-support off), the same message sent by the user from the top-level interpreter necessarily implies an overhead. Considering the time taken for the user to type the goal and read the reply, this overhead is of no practical consequence.

When a message is not cached, the number of predicate calls depends on the number of steps needed for the Logtalk runtime engine to lookup the corresponding predicate scope declaration (to check if the message is valid) and then to lookup a predicate definition for answering the message.

Processing time

Not all predicate calls take the same time. Moreover, the time taken to process a specific predicate call depends on the Prolog compiler implementation details. As such, the only valid performance measure is the time taken for processing a message.

The usual way of measuring the time taken by a predicate call is to repeat the call a number of times and than to calculate the average time. A sufficient large number of repetitions would hopefully lead to an accurate measure. Care should be taken to subtract the time taken by the repetition code itself. In addition, we should be aware of any limitations of the predicates used to measure execution times. One way to make sense of numbers we get is to repeat the test with the same predicate using plain Prolog and with the predicate encapsulated in a module.

A simple predicate for helping benchmarking predicate calls could be:

benchmark(N, Goal) :-
    repeat(N),
        call(Goal),
    fail.

benchmark(_, _).

The rational of using a failure-driven loop is to try to avoid any interference on our timing measurements from garbage-collection or memory expansion mechanisms. Based on the predicate benchmark/2, we may define a more convenient predicate for performing our benchmarks. For example:

benchmark(Goal) :-
    N = 10000000,                   % some sufficiently large number of repetitions
    write('Number of repetitions: '), write(N), nl,
    get_cpu_time(Seconds1),         % replace by your Prolog-specific predicate
    benchmark(N, Goal),
    get_cpu_time(Seconds2),
    Average is (Seconds2 - Seconds1)/N,
    write('Average time per call: '), write(Average), write(' seconds'), nl,
    Speed is 1.0/Average,
    write('Number of calls per second: '), write(Speed), nl.

We can get a baseline for our timings by doing:

| ?- benchmark(true).

For comparing message sending performance across several Prolog compilers, we would call the benchmark/1 predicate with a suitable argument. For example:

| ?- benchmark(list::length([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], _)).

For comparing message sending performance with predicate calls in plain Prolog and with calls to predicates encapsulated in modules, we should use exactly the same predicate definition in the three cases.

It should be stressed that message sending is only one of the factors affecting the performance of a Logtalk application (and often not the most important one). The strengths and limitations of the chosen Prolog compiler play a crucial role on all aspects of the development, reliability, usability, and performance of a Logtalk application. It is advisable to take advantage of the Logtalk wide compatibility with most Prolog compilers to test for the best match for developing your Logtalk applications.