Messages
Messages allow us to ask an object to prove a goal and must always match a declared predicate within the scope of the sender object. Note that sending a message is fundamentally different from calling a predicate. When calling a predicate, the caller decides implicitly which predicate definition will be executed. When sending a message, it is the receiving object, not the sender, that decides which predicate definition (if any) will be called to answer the message. The predicate definition that is used to answer a message depends on the relations between the object and its imported categories and ancestor objects (if any). See the Inheritance section for details on the predicate declaration and predicate definition lookup procedures.
When a message corresponds to a meta-predicate, the meta-arguments are always called in the context of the object (or category) sending the message.
Logtalk uses nomenclature similar to other object-oriented programming languages such as Smalltalk. Therefore, the terms query and message are used interchangeably when referring to a declared predicate that is part of an object interface. Likewise, the terms predicate and method are used interchangeably when referring to the predicate definition (inside an object or category) that is called to answer a message.
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 predefined 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 and meta-call context by using the []/1 delegation control construct:
..., [Object::Message], ....
This control construct can only be used within objects and categories
(at the top-level interpreter, the sender is always the pseudo-object
user
so using this control construct would be equivalent to using 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. 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 control construct 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 (plus the (*->)/2
soft-cut
control construct when provided by the backend Prolog compiler). 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 predicates
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 as 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
Assuming the events flag is set to allow
for the
object (or category) sending a message using the
(::)/2 control construct, two events are generated,
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 rationale 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 :-
...,
% get self reference
self(Self),
% send a message to self using (::)/2
Self::Message,
... .
If we also need the sender of the message to be other than the object containing the predicate definition, we can write:
Predicate :-
...,
% send a message to self using (::)/2
% sender will be the pseudo-object user
self(Self),
{Self::Message},
... .
When events are not used, it is possible to turn off event generation globally
or on a per-entity basis by using the events
compiler flag to optimize
message-sending performance (see the Event-driven programming section for more
details).
Sending a message from a module
Messages can be sent to objects from within Prolog modules. Depending on the
backend support for goal-expansion and on the optimize
flag being turned on, the messages will use static binding when possible. This
optimization requires the object to be compiled and loaded before the module.
Note that the module can be user
. This is usually the case when sending
the message from the top-level interpreter. Thus, the same conditions apply
in this case. Note that loading Prolog modules using Prolog directives or
built-in predicates necessarily limits the range of possible optimizations
for messages sent from the modules.
Warning
If you want to benchmark the performance of a message-sending goal at the top-level interpreter, be careful to check first if the goal is pre-compiled to use static binding; otherwise you will also be benchmarking the Logtalk compiler itself.
Message sending performance
For a detailed discussion on message-sending performance, see the Performance section.