Research: Aspect-Oriented Programming and CARMA

My work in the field of Aspect-Oriented Software Development is mostly on the improvement of crosscut languages. For this I developed my own Aspect-Oriented Programming environment, CARMA, which is used mostly as a research vehicle to investigate the impact of crosscut language features on AOSD.

Check below for a list of publications on or involving CARMA.

Overview

CARMA: Aspect-Oriented Programming with a SOUL

CARMA (formerly Andrew) is a language for Aspect-Oriented Programming based mostly on the AspectJ language but which makes use of logic meta programming for the specification of crosscuts. Aspect-Oriented Programming aims to provide modularization techniques for aspects. Aspects are concerns (or features) of a system whose implementation is difficult if not impossible to cleanly localize in a single module using current techniques, instead the implementation tends to be scattered through the modules of other concerns. This inevitably leads to well-known problems of bad modularization: difficulty in reading the code, maintaining it, evolving it etc. One of the observations made in AOP is that a cause for the problem is that all pre-AOP modularization techniques use a module composition mechanism based on one module calling another: whether the modularization is into modules, classes, procedures or something else they are composed respectively by invoking the interface of another module, sending a message to another object or calling another procedure. Therefore AOP research has produced a number of modularization techniques based on the opposite composition mechanism: aspects can often be cleanly localized by using modules that specify when they should be invoked by other modules. The "when to be invoked" points are known in AOP lingo as "joinpoints", and specifying these joinpoints in an aspectual module is done through a crosscut specification written in a crosscut language.

As CARMA is an AOP-extension of an object-oriented language, its joinpoint model is, like AspectJ's, based on the key events occuring in object-oriented programs: sending and receiving of messages, and inspecting and changing of state. At every such event joinpoint, an aspect can intercept and execute some other code right before or after the actual execution of the joinpoint. This other code is known as the "advice" of the aspect, and is in CARMA specified simply as code in the object-oriented language. The specification of exactly which joinpoints to intercept, the "crosscut", is in CARMA written in a crosscut language based on logic programming.

The use of a crosscut language based on logic meta programming gives CARMA several advanced features. From logic programming it gets the use of unification as a more advanced wildcard mechanism than what is supported in other crosscut languages, the use of logic rules for writing reusable crosscut specifications, and the use of defining multiple rules for the same predicate for writing variants of a crosscut specification. From logic meta programming it furthermore gets features for writing crosscut specifications based on structural properties of the program being crosscut. The need for and advantages of these features are further explained in the following sections which give an overview of several applications of CARMA and research questions in which it was involved. The very next section first gives an overview of the basics of CARMA.

CARMA Basics

Because of the use of a logic-programming-based crosscut language, a CARMA crosscut is written as a logic query over the set of all joinpoints occuring in the object-oriented program. The query can make use of a number of joinpoint predicates, predicates stating conditions over joinpoints, which form the heart of the CARMA language. The most basic predicates are shown in the following table. Note that two versions are given of each predicate: one in the old Prolog-like syntax that was previously used in our logic language SOUL, and one in the newer syntax which came about as a result of our linguistic symbiosis research.

Basic crosscut predicates in the CARMA crosscut language for expressing conditions on joinpoints
Type of joinpoint Crosscut predicate in old syntax Crosscut predicate in new syntax (In development)
Message reception reception(?jp, ?selector, ?arguments) ?jp isReceptionOf: ?selector with: ?arguments
Message send send(?jp, ?selector, ?arguments) ?jp isSendOf: ?selector with: ?arguments
Assignment assignment(?jp, ?varName, ?oldValue, ?newValue) ?jp isAssignmentTo: ?varName from: ?oldValue to: ?newValue
Reference reference(?jp, ?varName, ?value) ?jp isReferenceOf: ?varName havingValue: ?value
Block execution blockExecution(?jp, ?arguments) ?jp isExecutionOfBlockWith: ?arguments

The predicates summarized above can be used to state a condition on the type of joinpoint and some data typically associated with that type of joinpoint. To show how these are used in a crosscut expression we will introduce advices first. An advice is a combination of a crosscut and some smalltalk code that should be executed right before or after every joinpoint matching the crosscut. An example of an advice in CARMA is:

before ?jp matching { ?jp isReceptionOf: #draw with: <> } do
  Transcript show: 'Executing draw'

The above advice expresses that any time an object receives a message #draw, the string 'Executing draw' should be printed to the transcript. The crosscut of the advice is specified after the keyword 'matching' and is enclosed in curly braces, in the example the crosscut consists of only a single condition specifying the joinpoint in the variable ?jp should be a message reception joinpoint with the associated data being the selector #draw and an empty argument list.

Pattern-Based Crosscuts

A major research question in which CARMA is involved is in how the coupling between aspects and classes can be minimized. Strong coupling between aspects and classes can arise either from the crosscuts or the advice bodies of the aspect. The question of minimizing the coupling of advice bodies and classes is mostly similar to the question of minimizing coupling between methods of different classes, as advice bodies are executable code similar to method bodies. Crosscuts are the really new part of AspectJ-like AOP and while one goal of AOP is to provide new means for improving code, crosscuts also provide new mechanisms that might lead to strong coupling. Crosscuts can lead to strong coupling between classes and aspects when they for example specify specific names of methods, variables, superclasses etc. of classes as conditions. Such strong coupling is to be avoided as it leads to problems when the classes are changed, the crosscuts may then no longer intercept joinpoints that they should intercept, or conversely they might intercept new joinpoints which they shouldn't intercept.

A good example aspect to illustrate the issue of strong coupling is the observer aspect. This is a simple aspect which is intended to intercept the joinpoints where an object is finished executing a method that changed its state so that other objects which depend on the state of the first object can be notified of the change. This is the same problem which is also tackled by the well-known "Observer design pattern" using a non-aspectualized solution, hence the name observer aspect. To illustrate the strong coupling issue we consider a case where the aspect needs to intercept the executions of state changing methods of a class Buffer.

A clear example of how a crosscut language can lead to strong coupling is to be found in some early crosscut languages in which it was only possible to specify the exact signature of a method whose execution joinpoints should be intercepted. The crosscut for our example aspect would then look like the following: (in AspectJ-like syntax)

pointcut changesState() :
   execution( void Buffer.put(int) ) ||
   execution( void Buffer.get() )

Slide from a presentation of CARMA illustrating the problem with enumeration-based crosscuts. Click the slide to step through the animation. A PDF document of the complete presentation is available as well.

The above crosscut specifies by name the methods 'put' and 'get' of the Buffer class, which are known to change the state of Buffer objects. With this crosscut the observer aspect is very strongly coupled to the specific version of the Buffer class that was around when the aspect was written: if the Buffer class is changed to include new methods 'putAll' and 'getAll', these will not be automatically captured by the aspect's crosscut!

If we take a closer look at the above example we see there's a large gap between the crosscut's semantics as we express it to other programmers using the name of the crosscut, and the semantics that is actually encoded in the conditions of the crosscut itself. The crosscut's name "changesState" reflects the intention of the pointcut pretty well, but the actual conditions of the crosscut simply state 'every joinpoint that is either an execution joinpoint of a method named put OR an execution joinpoint of a method named get'. Such crosscuts can be best described as being purely enumeration-based: they simply enumerate a number of places where the aspect should crosscut without describing the actual semantics of what these places have in common. In contrast, a pattern-based crosscut would specify that semantics as a more general pattern of conditions the joinpoints should meet.

The ability of a programmer to write pattern-based crosscuts is of course strongly influenced by the crosscut language. In the example language used above, there is simply no other way of writing the crosscut other than using an enumeration. So the research question is what language features should a crosscut language offer in order to allow programmers to write pattern-based crosscuts?

As mentioned in the introductory section, the fact that CARMA is based on logic meta programming gives it several advanced language features that can be used to write good pattern-based crosscuts. An example of a very much improved crosscut for the observer aspect example is shown in the following advice:

after ?jp matching
  reception(?jp, ?msg, ?args),
  inObject(?jp, ?obj),
  objectClass(?obj, ?class),
  changesState(?class, ?msg),
  not(caller(?jp, ?obj))
do
  observers notify

The crosscut expresses that a joinpoint matching it should be a message reception joinpoint of a message ?msg, that is received by the object ?obj, where the class of that object is ?class and the method that implements that message on the class is known to be potentially state changing. The joinpoints are furthermore restricted in the last condition of the crosscut to those message receptions that were not sent by the object itself. The predicates "reception", "inObject", "objectClass" and "caller" are all standard predicates provided by CARMA. The "changesState" predicate is a predicate that the implementer of the observer aspect defined himself. It defines the most important condition in the above crosscut by relying on the advanced features of CARMA, the rules defining this predicate are:

changesState(?class, ?methodName) if
  shadowIn(?class, ?methodName, ?sp),
  assignmentShadow(?sp, ?variable)

changesState(?class, ?methodName) if
  shadowIn(?class, ?methodName, ?sp),
  messageShadow(?sp, ?rcvr, ?msg),
  selfReceiver(?rcvr),
  changesState(?class, ?msg)

The first of the above rules expresses that any method of a class is (potentially) state changing if it contains an assignment, the second rule expresses that furthermore every method is also state changing if it contains a message that invokes a method that is state changing. (Note that we say 'potentially state changing': it is theoretically impossible to derive whether a method will actually change the state, as this can depend on if-tests etc., so we look for methods that are potentially state changing meaning.)

With the above crosscuts and rules for the 'changesState' predicate, the observer aspect will always capture the 'state changing' methods of a class. Thus when a programmer changes the Buffer class's 'put' method to 'putElement', or when she adds a method 'putAll' or 'wipeBuffer', there is no need anymore to remember to edit the aspect as well. Furthermore, the observer aspect can now be used on other classes than the Buffer class as well. The coupling of the observer aspect and the classes it affects is thus greatly minimized, improving the maintainability and evolvability of code that uses it.

If you would like to learn more about CARMA's advanced language features in relation to pattern-based crosscuts, we suggest you read the paper Arranging language features for more robust pattern-based crosscuts which you can find in the related publications section below.

Open Weaver

Another way to look at CARMA is that the combination of logic and meta programming leads to an Open Weaver. This was a viewpoint on CARMA which we explored before the pattern-based crosscuts, but it is very closely related to it. The Open Weaver viewpoint is that CARMA can be used to build up a library of new joinpoint predicates that extend the basic predicates provided by CARMA to define more specialized types of joinpoints. This is useful for defining joinpoints specific to the domain of the program or which are based on the execution of common programming idioms. This is strongly related to pattern-based crosscuts as the definition of these joinpoints needs to be captured using a pattern-based rather than enumeration-based crosscut.

The ability to define new joinpoint predicates is especially of use in languages such as the one CARMA has been originally defined for: the Smalltalk OOP language has a lightweight but very flexible syntax which means that a lot of concepts that other languages define specialized syntax for are handled as programming idioms in Smalltalk. For example, while Java defines the specialized try/catch syntax for exception handlers, this is handled in Smalltalk using message sends: a message 'on:do:' can be sent to a block of code, with an exception class and another block as argument. In response to the message, the first block evaluates itself and when an exception of the given class occurs it executes the second block. Another example is that Java and C++ use a special type of method called the 'constructor' to create and initialze objects and the special 'new' syntax for invoking the constructors, while in Smalltalk the equivalent of 'new' is simply a message send like any other. 'Object initialization' then is handled by a common idiom whereby the 'new' message is overriden on the class to call the original 'new' from the superclass, and send a message 'initialize' to the newly created object before it is returned. In cases where object construction or initialization depends on parameters, the idiom is still the same: there is a class message which creates an object and sends it a message to tell it to initialize itself, passing it the necessary parameters.

In crosscut languages for Java, object initialization and exception handling are also joinpoints that can be intercepted by crosscuts. In a crosscut language for Smalltalk, the question is how to include such joinpoints in the language as well. Obviously one could make a simple crosscut language which also provides such joinpoints by hardcoding the idiom on which they are based. But the problem with idioms is that they are open to change, local variations and programmer preferences: the idioms described above for exception handling and object initialization are the most commonly occuring in Smalltalk, but there are common variations as well. Therefore:

Because idioms are open to change, their definitions in the crosscut language should be open to change as well.

Note that this problem is not specific to Smalltalk or languages with flexible syntaxes. Programming idioms occur in programs in any language. To take an example from Java and C++: in these languages iterating over a collection of objects with an Iterator in a for-loop is a very common idiom. More generally, there will be idioms that are specific to programs instead of languages, which can not possibly be captured as joinpoints in the crosscut language by the designer of the language.

Because of CARMA's combination of logic and meta programming, joinpoints for the execution of idioms can be defined as rules as well, allowing them to be changed. An example for the object initialization idiom is that we can define a predicate 'initialization' which captures the joinpoints that are assignment joinpoint occuring during object initialization:

Rule initialization(?jp, ?class, ?varName, ?initVal) if
	class(?class),
	instVar(?class, ?varName),
	assignment(?jp, ?varName, ?preInitVal, ?initVal),
	within(?jp, ?class, ?selector),
	instanceCreationMessage(?class, ?selector).

Rule instanceCreationMessage(?class, ?selector) if
	metaClass(?class, ?meta),
	selectorInProtocol(?meta, ?selector, ['instance creation'])

The implementation of the initialization predicate uses the 'within' and 'instanceCreationMessage' predicates to restrict its solutions for the '?jp' variable to only those assignment joinpoints that occur within a method with name '?selector' that implements a part of the initialization idiom. This is defined by the 'instanceCreationMessage' predicate as those method names that are implemented as class messages in the 'instance creation' protocol, which is how the initialization idiom is normally implemented.

Generic Advices

Aspect-Oriented Refactoring

Ternary Logic for Two-Phase Weaving

Artefacts

I've Implemented CARMA in Squeak Smalltalk and VisualWorks Smalltalk. The Squeak version is the original prototype which was later ported to VisualWorks. The Squeak version is no longer in active development and was never entirely finished, furthermore it relies on a pretty old unstable version of SOUL. It is available for download here so that you can toy around with it, but you should definitely not rely on it for anything more than that. The VW version will be made available here in the future as well, but currently is only available on request.

You have two options for downloading and installing the Squeak version of CARMA, either you download only the source code which can then be filed in into your Squeak image. You are strongly encouraged to use Squeak version 3.0. (CARMA won't work on older versions, it might on newer but this has not been tested.) Alternatively you can download a full Squeak image which you just have to open in Squeak and CARMA will be ready to go. This should also probably be ran on a Squeak 3.0 Virtual Machine though.

Download CARMA (Andrew) for Squeak source distribution (size: 252 kb, version 5 beta 3 (first public release))
Download Squeak image with CARMA (Andrew) preinstalled (size: 5,7 Mb, version 5 beta 3 (first public release))

Related Publications

Note that this list is infrequently updated and may therefore not contain my most recent publications regarding aspect-oriented programming, you can always check out my publications page as well.

Related Work