|Did you know ...||Search Documentation:|
|Pack aop -- README.md|
Aspect-oriented programming (AOP) is a technique for enabling software engineers to separate concerns (modeled as an aspect) within their code. The intent of such separation is reduction of coupling and simplification of code in each aspect: ideally the author of an aspect doesn't require deep awareness of other aspects in order for their own code to function correctly.
aop library is not a complete implemention of AOP, but is instead an opinionated perspective tailored to Prolog. As such,
aop adds the following constructions to Prolog:
This library is packaged as a SWI-Prolog package, and as such is easily installed from within the SWI-Prolog shell:
Within your applications source, simply load the library as normal:
Note due to defintion and use of
goal_expansion in the library, you may receive these warnings when loading the library. They are harmless and have no other ill side effects:
... Warning: Local definition of user:goal_expansion/2 overrides weak import from aop_dsl ... Warning: Local definition of user:term_expansion/2 overrides weak import from aop_dsl ...
Several principles guide the design choices in the
aopcan use the
::operator to send a message on the right hand side (expressed as a compound prolog term or an atom) to the receiver on the left hand side.
::is just an operator defined as
send_message(Object, Message). Further, a default implementation of
send_messageeventually devolve into calls to native Prolog predicates extended with additional arguments to support messaging passing and aspect oriented styles of progrmaming. As all such message sends are just Prolog goals, reasoning about program behavior is generally the same if message sends were expressed as goals.
This library provides a DSL for structuring code for an aspect-oriented style, in line with the opionions of its design.
A typical source file might look something like this:
% Module declarations are still useful, although exports are not :- module(my_aop_module, [ ]). % Load this aop library :- use_module(library(aop)). % Starts definition of a new aspect. :- new_aspect(my_aspect). % Defines a new object provided by this aspect; variables in the term % can generalize its applicability. The second optional argument % is an array of accessors, useful for extracting the arguments % to the object's expression. Because accessors are in the same % statement as the object itself, the same variables are available. :- new_object(my_object(Name), [ % Defines a method :for accessing the object's name parameter, e.g., % my_object(foo)::name(N) will bind N to foo name(Name) ]). % All clauses inside the object definition become methods on the object. Thus, this method % can be used like this: my_object(foo)::print. In this case there are no paraemters to the goal. print :- % Inside a method, the unary :: operator is equivalent to sending the message % to the current object. Usef of the unary :: operator is not required, but a % convenience. ::name(Name), % Calling regular Prolog goals is natural and unchanged from how its % done in a non-aop source file format("~q~n",[Name]). % Ends the currently open object definition. :- end_object. % Objects are not required to have parameters to their term, or accessors. :- new_object(printer). print_object(Object) :- % Invoking a method on another object uses the binary :: operator Object::print. :- end_object. % This is a contrived sample just to show more code; its not intended to be an % indicator of how to design code with aop :- new_object(sample) :- print_it(SomeObject) :- printer::print_object(SomeObject). :- end_object. % Ends the currently open aspect definition. :- end_aspect.
This library adds several new predicates and directives for structuring AOP code.
:- end_aspect- Brackets an aspect definition, inside which one or more object definitions should appear.
:- end_object- Brackets an object definition, with all predicates defined inside interpreted as methods on the defining object.
:- end_object- Brackets an extension of an existing object, adding new methods not originally present. USeful for extending an existing object, espeically if defined in a different aspect.
dynamicdirective in ordinary prolog: indicates that objects may implement this method.
:- end_object- A helpful trick for creating "nested" objects: that is, an object whose first term argument is actually the enclosing object in which this appears. Thus,
foo(Bar)if nested inside an object such as
baz(Wazoo), then the real object being declared is actually
::operator indicates that the method takes an explicit This parameter as the first argumenbt of the head -- thus making bindings of that variable available in the body of the clause. The variable will be bound to the receiving object, and can be any variable name (e.g., the use of This as a name is a just a convention).
this(This)` - in the body of a method, an alternative method for accessing the receiver is this built-in method. Again, the variable name This passed as an argument is just a convention, and any variable (or expression) can be used.
at(This, BeforeOrAfter, Message)` - Defines an event handler to be invoked synchronously
afterthe message on the receiving object.
on(This, BeforeOrAfter, Message)` - Defines an event handler to be invoked asynchronously
afterthe message on the receiving object.
atdirectives will only be invoked if this directive is also present for the intended message.
use_aspect(foo/bar/baz)- A helper method for loading aspects into an application, similar to
:- use_module(foo). The
aoplibrary by default adds an `./aspects` directory into the current file search path, so that
use_aspect(foo)can become the traditional
use_module(aspects(foo))which is equivalent to `use_module(./aspects/foo)`. The key difference is that when the aspect name contains a
/, then each containing step is loaded first. Thus, the compound
:- use_aspect(foo/bar/baz)translates to the following:
:- use_module(aspects(foo)). :- use_module(aspects(foo/bar)). :- use_module(aspects(foo/bar/baz)).
aop is not intended to alter the basic behavior of Prolog applications. In essence, its just providing some semantic sugar to structure the application in a more object and aspect oriented style.
Message sends (for example) using the binary
:: operator translate to a call to an internal
send_message predicate. Thus:
send_message(Object, some_method(Parameter1, Parameter2))
send_message predicate (internal to the
aop library and not intended to be overwritten or extended with new clauses) attempts to efficiently locate a suitable clause for the method. The basic mechanics involve translating
some_method(Parameter1, Parameter2) into an equivalent goal with 1 additional parameters in second position:
aop:do(Aspect, Object, some_method(Parameter1, Parameter2)), where Object was the original receiver in
send_message. The reason for these semantics is two-fold:
For example, if the
some_method definition originally appears in a
some_aspect aspect declaration which also contains a
some_object(Foo) declaration, then
send_message will eventually find this clause:
aop:do(some_aspect, some_object(Foo), some_method(Parameter1, Parameter2)) :- <body of clause goes here per usual>.
Because methods for a given object may be implemented in many different aspects (that's the point of the AOP style), modules take on a little less meaning in an AOP code, as imports and exports of methods no longer apply to objects, aspects, or methods: methods are essentially "global" to the object for which they are defined. Aspects and objects are global to the application that has loaded them. Structuring code in modules is still recommended, as much of the source code loading machinery of Prolog leverages it, and it will help with interfacing to traditional, non-AOP Prolog code.
For modules that define aspects, its usually a good idea to name the module after the aspect: for example, if the aspect is
some_aspect(Foo) then a good convention is to name the module
some_aspect. When multiple source files (and thus multiple modules) are necessary to fully implement an aspect, then sub-modules loadable with `use_module(some_aspect/internals`) can have the module name
Note that objects themselves provide an alternative to modules for creating a modular application. Because each object essentially defines a namespace for dispatching methods, then objects themselves can be used to create logical boundaries between units of code. For example, one can easily add common routines for manipulating lists to a
lists object, thus placing such routines in a logical module.
For situations where introspection of the aspects, objects, and methods available in an application,
aop defines several predicates and common methods available universally.
Note that for each of these, there are internal objects or terms used to described them, and the nature of those terms may change over time -- thus accessing details (e.g., name of an aspect) is best done not with unification against the object but by using defined accessors on the object.
current_aspect(AspectName)- When called successively, will iterate over all names of loaded aspects in the application.
current_enabled_aspect(AspectName)- When called successively, will iterate over names of all enabled aspects.
disable_aspect(AspectName)- Aspects can be indepdently enabled or disabled dynamically: methods in a disabled aspect have no effect, and evaluation will fail.
current_object(Object)- Returns the internal description of the object; the accessors `::
object(Object)`, and `::
original_module(Module)` are all available for further inspection.
current_method(Method)- Returns the internal description of a method. The following accessors are available:
aspect(Aspect)` - Aspect in which the method was declared
object(Object)` - Object in which the method was declared
name(Name)` - The name of the method (extracted via functor/2)
arity(Arity)` - The arity of the method as declared (note: does not include any internal arguments passed to implementing clauses)
declaring_module(Module)` - Module where the method was declared
predicate(Name/Arity)` - Utility method, combinging
method(Method)` - Iterates over the methods defined on the receiver
apply(Partial, Args)` - Constructs a full message by adding Args onto the end of the argument list in Partial, and then invoking the created message on the receiver
listing(MethodPattern)` - Lists clauses of methods defined on the receiver
Not only do objects provide scoping for methods, but they can also provide scoping for arbitrary facts or rules. The
aop library provides on all objects the basic definitions necessary for asserting / retracting facts or rules on the receiving object. Each such assertion is expanded to include the aspect and object as initial arguments in the head of the asserted clause. Assertions are added to the module where the
new_object definition for the object originally appeared.
Available methods with analogous meanings as the base prolog definitions, but scoped to the object include the following: