Aspect Oriented Programming

Aspect Oriented Programming in Heron

Heron support aspect oriented programming (AOP) using metaprogramming techniques. It is a simple, yet extremely powerful mechanism. Every function -- except for external module functions -- in Heron has implicit meta-expansion statements at the beginning and another at the end. In other words the following function:

  Fxn()
  {
    // ... do something
  }

is rewritten by the compiler as:

  Fxn()
  {
    #_before
    // ... do something
    #_after
  }

It is the responsibility of a standard library to provide definitions of _before and _after code-blocks. These can be overridden by the programmer as a type-alias using define in an imported unit, the local unit, or in the local class.

It should be noted that the Heron name resolution rules give precedence to symbols in explicitly imported modules over implicitly imported modules. In other words any name in the standard library can be given a new meaning, which takes precedence. Heron2C does not fully implement these rules.

AOP Example: Tracing

An almost trivial, yet useful, application of AOP in Heron would be to trace the calls in and out of functions. This could be done by defining _before as below:

  define _before : {
    P("entering function " + $_fxn_name);
  };

  define _before : {
    P("leaving function " + $_fxn_name);
  };

The function P(String) is a standard library function that writes a string followed by a newline to the standard output stream, much like the C function puts(char*). The expression $_fxn_name converts the compile-time function _fxn_name to a _string value which returns the value of the local function.

Using Aspect Oriented Programming with Contracts

AOP provides a simple mechanism to implement invariant checking, which is a key obstacle to using Programming with Contracts (PwC) techniques in languages like C++ and Java. An invariant is a condition which must evaluate to true before and after every member function in a class, but only after the constructor and before the destructor.

To check an invariant, there are several different approaches. One way is as follows:

  define _before : {
    #_invariant
  };

  define _after : {
    #_invariant
  };

We would then define _invariant as follows:

  define _invariant : {
    CheckInvariant();
  };

We then define a default version of CheckInvariant which does nothing:

  CheckInvariant() {
  };

Then any class which supplies a CheckInvariant() function, will automatically have the invariant checked before and after every function.

There is one big problem though so far, CheckInvariant itself will call CheckInvariant, thus leading to a case of infinite recursion. In order to prevent this we have to do a bit of compile-time magic to prevent infinite recursion.

  define _invariant : {
    #meta_if // conditonal compilation operator
    <
      <
        s_neq<_function_name, "CheckInvariant">,
      >,
      {
        A(CheckInvariant()); // assert CheckInvariant returns true
      },
      {
        // empty code block
      }
    >
  };

What is Aspect Oriented Programming?

Aspect Oriented Programming is a technique of expressing and separating crosscutting concerns in software. An aspect is a modular representation of a crosscutting concern. Aspects can not be easily expressed using Object Oriented Programming techniques, because they represent repsonibilities that do not fit well into a hierarchical structure. The key to understanding AOP is to realize what constitutes a crosscutting concern.

Some examples of concerns, as outlined in the paper by Hursch and Lopes, are algorithms, data organization, process synchronization, location control, real-time constraints, persistence, and failure recovery. I would add debugging and specification verification, e.g. assertions and design contracts, to this list.

A crosscutting concern is one which is interwined or tangled with that of another in a way which can not be easily separated using OOP techniques alone. A simple example is the concern of function tracing. I could express in natural language the requirement to trace functions in one sentence:

Upon entry into a function print to the console the name of that function.

In languages which don't support some form of AOP, this kind of concern would require a unique statement to be placed at the beginning of every single function.

Another example of a crosscutting concern occurs when it is desirable to verify a class invariant. A class invariant is a condition which is required to be true throughout the lifetime of an object. Expressed in natural language:

Upon entry and exit of every function for a given class verify that the class invariant evalutates to true.

Again, there is no way in a language like C++ or Java to express such a concern in a single location.

The purpose of AOP is to provide us with a way to express an aspect ( crosscutting concern ) in a simple and modular manner.

For more information on AOP I recommend the following papers:

Heron AOP compared to AspectJ

There are several new aspect oriented extensions to various existing languages but much of the groundbreaking work in AOP has been originated from AspectJ. Many of the current AOP products and language extensions are modeled on AspectJ so it is appropriate to use AspectJ as point of comparison.

The AspectJ language is used to describe join-points, pointcuts and advice. In AspectJ the aspect is the application of specific code ( the advice ) at a pointcut ( set of join-points ). What is particularly interesting to many users is that it has a built-in language for expressing pointcuts. In Heron the difference is that these features have to be implemented by the programmer or the user using metaprogramming techniques, but all the neccessary core facilities for AOP are provided.

The goal of Heron support for AOP was to provide the bare minimum of features to still be useful yet allow programmers take advantage of AOP techniques in their code. The analogy in Heron for the AspectJ concept of "advice" is simply the _before and _after code blocks themselves. The analogy for pointcuts in Heron are scope. There are three levels of scope where the _before and _after code blocks can be applied: global scope, module scope, or class scope. This is limited, but sufficient. The application of advice to specifically named functions like in AspectJ can be done through metaprogramming techniques such a conditional compilation based on the name of the function. Even more sophisticated pointcut expressions such as the AspectJ concept of cflow can also be easily achieved through application of metaprogramming techniques.

Heron currently lacks some more interesting built-in meta-functions for querying information about the local type and function which would make it easy to define more sophisticated advice code, such as facilities for querying the number, names and types of parameters. This functionality is previewed in future Heron releases. Heron also currently lacks _except code blocks, to deal with exceptional cases. This is also previewied for a future Heron release.