Is "post OO" just over?

While studying the conference program of the upcoming OOPSLA 2006 I discovered under the category "essay" an author who has quite something critical to say about AOP:

Aspect-oriented programming is discussed as a promising new technology. Like object-oriented programming, it is beginning to pervade all areas of software engineering. With its growing popularity, practitioners and academics alike are beginning to wonder whether they should start looking into or it, or otherwise risk having missed an important development. The author of this essay finds that much of aspect-oriented programming's success seems to be based on the conception that it improves both modularity and the structure of code, while in fact, it actually works against the primary purposes of the two, namely independent development and understandability of programs. Not seeing any way of fixing this situation, he thinks the success of aspect-oriented programming to be paradoxical.

This is not just another internet rant about the latest PL hype but the author, Friedrich Steimann, had done interesting work about AOP before. In particular his latest paper about typed AOP:

AOP and the antinomy of the liar

but also his award winning former critical AOP review:

Domain models are aspect free

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

AOP is only successful in

AOP is only successful in that many researchers have received funding to investigate it. In the real world, AOP is entirely unsuccessful: No mainstream programming language has integrated major AOP features. And this is the correct state of affairs because, as the author more eloquently states, the techniques that are unique to AOP are a collection of dangerous hacks.

Fortunately, many of the original goals of AOP (implementing features that cross-cut data types and programs) are more soundly satisfied by the latest work on generic programming, Haskell typeclasses, etc.

I'm tempted to object to this...

but only because I think you're praising AOP with faint damns.

AOP vs Concerns

I think the most fundamental mistake is to confuse AOP (especially as embodied in AspectJ) and Dijkstra's separation of concerns, and Parnas' information hiding, which seems to be commonly done, especially in AOP propaganda. As both Dijkstra and Parnas have emphasized in their writings, separation of concerns and information hiding are fundamentally design-time concepts. Only crude approximations can (currently) be done in any programming languages using safe, organized way of programming.

Another issue is one of anti-modularity of many AOP features; because advice depends on implementation details of a particular routine (method, etc), those implementations details are effectively leaked (breaks information hiding) and de facto become part of the routine's interface (otherwise a change would break the aspect). But worst probably is that to understand what any particular routine does (at least in AspectJ), you have to understand all the advice that is applied to that routine, in other words, local reasoning does not help understanding. Granted, without aspects, the same routine would be huge and as difficult to understand, but at least the reasoning would be local!

This is not to say that AOP is value-less, by any means. AspectJ is as good and as bad as many breakthrough languages, with features that are both useful and astoundingly dangerous. LISP did have setq and dynamic scoping, goto was ubiquitous, etc. Just as I personally consider ML and Haskell to be ``tamed'' versions of Scheme, we are still several years away from having a similar understanding of aspects (though the research has clearly started, as witnessed by AspectML, and the myriad other systems). In my not-at-all-humble opinion, the current systems take the basic ideas of advice and pointcut much too literally, and seem to have forgotten the wise words of Dijkstra and Parnas. But that's a common symptom in computer science -- any paper more than 5 years old can't possibly say something useful, can it?

emacs lisp

I don't know if you would call emacs lisp mainstream, but it has aspect oriented features that have been in use for years. I do recall (from reading comp.emacs.devel) that RMS isn't very happy with it and is looking to rewrite many apps/libs that use aspects to improve code quality.

I think aspects are interesting, but they could really hinder understandability. Using them to allow for slight modifications in libraries *might* be ok, but for overall development I don't think I could wrap my head around them enough to find them more useful than more understandable methods (which I would consider possibly hook
functions, or something of the sort).

--
kruhft

PILOT

The first mention of AOP features, i.e. the notion of advice, is Warren Teitelman's PhD thesis from 1966 - see ftp://publications.ai.mit.edu/ai-publications/pdf/AITR-221.pdf This itself seems to have been influenced by John McCarthy's advice taker, but I am not sure whether that's actually true. Warren Teitelman's notion of advice became part of Xerox PARC's Interlisp, apparently found its way to MacLisp, then Flavors, LOOPS and finally CLOS. Gregor Kiczales was involved in the CLOS specification, so that's where he probably got this ingredient of AOP from. MacLisp was also a precursor of emacs lisp.

Dylan was mostly derived from CLOS but left out a number of features that were apparently deemed too complex and/or confusing, among them the CLOS notion of method combinations, that is, before/after/around advice. That's probably an early example of a rejection of (some elements of) AOP.

(AOP <= HOF)?

Is it just me, or is AOP an example of something that would be so much easier to implement merely by using "wrapper" higher-order functions, if only the language supported them? I admit I don't comprehend it in depth.

Pretty much

except that those higher-order wrappers need to be able to be created after the wrapping function, and applied independently without altering the call sites. That's the crux of it, and what makes AOP so easy to abuse.

That would worry me

It would no longer be possible to reason about the behaviour of a piece of code by looking at it. Some aspect somewhere else might silently be messing with it.

I can imagine an IDE colorizing or something to warn me an aspect is applying - and I could imagine trying to edit the same code in vi, and being unable to trace a bug in what looks like a bug-free function.

I prefer the principle that code should be explicit. If you want to add hooks for pre-and-post invocation code, you can do that, and a sufficiently powerful language will let you do it cleanly.

Actually...

... this is his latest paper: Avoiding Infinite Recursion with Stratified Aspects (sorry that I use the cache but the sable servers are down at the moment) Here we just comment on another shortcoming of AspectJ. It's the implementation to overcome the problems stated in "anatomy of a liar". Eric - who still believes that AOP done the right way is a good idea

Stratification is not always a good idea

Hi Erik,

look at this code:

fac 0 = 1
fac n = n * (fac n)

It is "evil" because it will generate an infinite recursion - the programmer made a bug. Would your advice to the programmer be to

a) replace the "n" in the recursive call by "n-1", or
b) propose "stratefied functions", where each function can only call functions on a higher layer, thereby making it impossible to write looping programs (and functions like fac, too)?

I discussed this "problem" with one of the authors when he presented this work at the FOAL workshop. I don't believe that the infinite recursion he talks about is any more or less a problem than infinite recursion due to recursive function or method calls. In fact, recursion is one of the most elegant and powerful concepts we have in computer science.

Apart from these "philosophical" considerations, the proposed stratification does not really solve the problem because an aspect may still indirectly advise himself by calling a function that triggers the aspect. Hence a simple refactoring of advice code into an external method is sufficient to break the model. This is how I remember it, at least. Let me know if this has changed.

Infinite recursion

Well, choosing (a), you can still get an infinite recursion:

fac 0 = 1
fac n = n * (fac (n-1))

since fac(-1) would count to negative infinity. :-)

I don't agree

Hi, Klaus.

I do agree with you that there might somewhere out there be an aspect or two where stratification costs you flexibility. However, up till now I still have not seen a single one. But that only as a side remark.

Regarding your functional example: This really does not compare. First of all, the infinite recursion *we* tackle is the one caused by an advice advising itself. This is always a mistake because such a recursion cannot be broken in a meaningful way. The problem here is that advice invocation is implicit. For the programmer it's hard to see right away if he made a mistake in the pointcut specification or now. Moreover without stratification he has to remember considering this issue *every time* he writes a pointcut.

In you example, (fac n) is invoked explicitly. We do not break such recursion. The problem here is mainly that the question arises of how you would break it in a meaningful way. (Maybe the compiler should still give an error message though, at least in a non-lazily evaluating language.)

Aspects are completely diffrent. They are invoked through inversion of control. An advice is never invoked explicitly. That makes sensible recursion on advice invocation impossible. Hence we proposed the solution to stratify aspects. Please note that we do *not* stratify usual function calls. So you can still use whatever recursion you want. You can just not get any more recursion through advice application. I know there are AspectJ advocates who claim that such recursion could be meaningful but by today I have not ever seen a single meaningful example. Feel free to proof me wrong.

Eric

An example

I did not assume you were stratifying functions; this was just an analogy to show that the "closure property" of a uniform, non-stratified mechanism is a wonderful feature and not a bug :-)

Whether this example makes sense or not is arguable, but since I brought up this example consider the problem of "outsourcing" the body of fac into an advice. So, we would have a "fac" method/function with an empty body (or: returning 0). The advice is triggered by any invocation of fac. If the parameter is >1, then the advice calls "fac" - a call which must of course trigger the advice in order to get the desired semantics.

This is contrived, of course, but I guess one can find more useful examples in the context of recursive funtions or loops. Like for example a naive exponential-time implementation of the fibonacci function, which is turned into a linear-time "dynamic programming" variant by a caching aspect.

By the way, why is a projection of advice on join point shadows as visualized by AJDT not sufficient? It clearly shows me when I have advice that may trigger itself, and I can then decide whether this was a bug or intended. Why make the programming model more complicated then necessary?

Making recursion explicit

The problems with merely allowing a programmer to check for bugs via the design tool vs. allowing them to state constraints on their program which are automatically checked is that a change elsewhere in the program may introduce an unexpected problem. This is particularly relevant on large development teams. After all, why make the programmers' tasks more complicated than necessary?

Stratification amounts to enforcing that the (implicit) call graph is a directed acyclic graph, true, but you can easily introduce explicit calls for the cases where you actually need cycles. This can be generalized in the following ways:

  • Allow advice to be explicitly defined as recursive, so it can implicitly call other advice at the same level, as well as at higher levels--corresponds to "letrec" as opposed to "let."
  • Implicit calls to higher levels (or same level, with letrec)--standard advice-taking
  • Explicit calls to the same level--standard function calls, and
  • Super-explicit calls to lower levels--down calls.

As Eric mentioned, the control flow graph can be used to determine whether a method is to be at one of the advice levels or the base level of the program. The reason I think down calls should be differentiated from explicit calls at the same level is that they are the most likely case to introduce errors due to separate development. One common use for down calls would be advice which rewrites an expression to a different expression which evaluates in the same context. In fact, such advice which operates without any I/O or runtime value dependence is essentially a macro, which can be simplified away at compile time.

Final note, I'm actually working on a language which uses this stratified calling convention (as a more sophisticated replacement for dynamic scoping), so you might see more about this if I ever get my act together.

regarding your last paragraph...

This is only partially correct. In general: Yes, there may be circumstances where the advice might still be triggered recursively. We thought about handling this via cflow in the future. Having said that, when you simply factor out advice code into a method, this method is still on the same layer as the advice and would hence not be matched. No recursion is possible this way.

But how do you know which

But how do you know which method is executed? Let's say the advice calls "Object.toString()" on some object known as "Object" only. Do you then consider all "toString()" implementations in your whole system? In general I believe you would need a global, approximating/conservative analysis of your program. Usually it is not a good thing if the static semantics (correctness) of your program depends on global properties...

We do not know which method

We do not know which method is executed. As mentioned above, we would had to use cflow to capture all of those cases, which is probably a good idea, especially given that some compilers now provide cflow with zero runtime overhead.

The property you mention has nothing to do with whether you factor our your advice code into a method or not. Without using cflow, when your advice calls a method on a lower level that is woven into, then the advice can match on the execution of this method and trigger itself. But this is independent of whether this call takes place directly or indirectly through a separate method.

As Eric said, factorial is a

As Eric said, factorial is a good example for recursive functions, but I am afraid it is not for aspects. Let me explain.

By definition advice encapsulates behavior which is crosscutting for several functions. This encapsulation is done by using new first class entities (advices) which are 'external' from the functions' point of view. Contrary to this a recursive function is 'closed' in itself. So the problem we address is the one where the 'external' entity and the function which it advises are the same, i.e. the advice advises itself. When we had our discussion after my FOAL talk I challenged you and Gregor to give me an example where an advice must advise itself in order to work. Unfortunately until now no such example was given. But to convince me that our proposal limits the expressiveness of AspectJ, I would like to see such an example.

For this, you first have to find an advice which advises itself in an meaningful way, and more so, whose advising itself is necessary to provide its functionality. Second the functionality provided by the advice has to be crosscutting, as this is the very nature of functionality encapsulated in an advice. Last but not least, there would have to be a meaningful, if not natural, termination criterion for the recursion.


'Hence a simple refactoring of advice code into an external method is sufficient to break the model. This is how I remember it, at least. Let me know if this has changed.'

This depends on the kind of refactoring or what you consider to be an external method. If you refactor the advice so that the extracted method resides in the same aspect, and therefore at the same type level, no recursion is possible in our approach. However, if you refactor the advice so that the extracted method resides in a class and the advice is capable of advising the new method in any way, infinite recursion can occur. We address this problem in our more recent paper "Avoiding Infinite Recursion with Stratified Aspects":

"We cannot avoid all recursion with our approach. In particular, we cannot avoid recursion resulting from an aspect accessing elements of lower levels (including classes), because for us, these accesses are indistinguishable from accesses by program elements on the same level, which are to be advised. However, our compiler could produce a warning wherever an aspect crosses levels by accessing elements from a lower level and this may result in recursion. In fact, the cflow analysis built into abc could be used to statically compute those warnings."

AOP in the real world

Tim Sweeney wrote:

In the real world, AOP is entirely unsuccessful

I have to disagree with this. Perhaps it's because I don't strictly think of AOP in terms of its (Gregor Kiczales' ?) formal definition. Rather, I think of aspecting as "a method for implicitly adding additional functionality to code". Take a look at EJB3 for a good counter-example to your statement. It's widely regarded as the most successful version of the specification, and it's primarily because of its "aspect-oriented" nature.

I've written several tools myself which operated over bytecode to transform the behavior of programs, and they've been wildly successful. Some things were as simple as implicitly adding contextual information to logging statements (current user, session-id, class, method, line number). Other things were much more sophisticated - for example, detecting state changes in objects, so I could transparently timestamp and record updates to system data for auditing purposes.

No mainstream programming language has integrated major AOP features.

There may not be any formalized aspecting system in any language, but both Java's annotations and C#'s custom attributes are used all over the place with tools to apply "aspects" to code. (Creating webservices from annotated methods is another example just off the top of my head).

I think one of the primary things I disagree with major AOP proponents about, is the idea that code must be totally oblivious to the aspects that are being applied to it, and that aspects should be applied via external pointcut declarations. I've personally found that my needs are almost always met better by explicitly declaring that aspects be applied to code at design time as I'm developing that code. That's why I find myself almost always using annotations in conjunction with my "aspects". Personally, I'm looking forward to the day where I can work with the program AST directly instead of rewriting bytecode.

A point from my discussion of Steimann's essay at OOPSLA

At OOPSLA 2006 I was the "discussant" for Steimann's essay. I think the essay itself is very interesting and thought provoking. However, I did raise several points about it. The key point I wanted to make in the discussion is that we should think of language features like quantification and obliviousness as continuous measures, so giving up a bit on them doesn't make a language non AOP. I think this goes a long way towards helping language designers see ways to solve the kind of software engineering problems Steimann is concerned about.

If this is interesting, then so perhaps you all would be interested in a recent paper that Curtis Clifton and I wrote that expands on this point. The paper is:

Multiple Concerns in Aspect-Oriented Language Design: Balancing Benefits is the Essence of Language Engineering. Department of Computer Science, Iowa State University, TR #07-01, February 2007.

ftp://ftp.cs.iastate.edu/pub/techreports/TR07-01/TR.pdf

Here's the abstract:

Some in the aspect-oriented community view a programming language as aspect-oriented only if it allows programmers to perfectly eliminate scattering and tangling. That is, languages that do not allow programmers to have perfect quantification and obliviousness are not viewed as aspect-oriented. On the other hand, some detractors of aspect-oriented software development view perfect quantification and obliviousness as causing problems, such as difficulties in reasoning or maintenance. Both views ignore good language design and engineering practice, which suggests trying to simultaneously optimize for several goals. That is, designers of aspect-oriented languages that are intended for wide use should be prepared to compromise quantification and obliviousness to some (small) extent, if doing so helps programmers solve other problems. Indeed, balancing competing requirements is an essential part of engineering. Simultaneously optimizing for several language design goals becomes possible when one views these goals, such as minimizing scattering and tangling, not as all-or-nothing predicates, but as measures on a continuous scale. Since most language design goals will only be partially met, it seems best to call them "concerns."

Welcome to Lambda, Prof.

Welcome to Lambda, Prof. Leavens!

While I am guilty of not having read the papers under discussion, it seems to me that viewing language design goals as "concerns" in this sense is really the only available option. While people may use an "all or nothing" rhetoric, language design requires by necessity decisions that try to optimize several (indeed, quite a few) goals.

We should keep in mind that even if the scale is continuous optimization may still be difficult to achieve, that the scale is in several important cases subjective, and that it often makes sense to decide to sacrifice some goals to a certain extent in order to better serve another set of goals. The latter is a useful technique, it seems to me, for keeping the language design coherent, a meta-goal in a sense. Examples of this design philosophy are pure FP languages, pure OO languages etc. One way of thinking about these approaches is to see them as deciding that one goal is an all-or-nothing concern - at least in the sense that it is non-negotiable. This reduces the dimensionality of the design space, yet still leaves a multi-dimensional continuous optimization problem, of course.

More comments on this discussion

Thanks for your welcome. I've been lurking for a long time :-), but haven't ever posted before.

I agree with your remark that "optimization may still be difficult to achieve, that the scale is in several important cases subjective, and that it often makes sense to decide to sacrifice some goals to a certain extent in order to better serve another set of goals." The paper is largely about having designers of aspect-oriented languages seeing some small sacrifices or compromises as good. But we also wanted to say that there really is a contribution that aspect-oriented languages make, that should be seen by others in the larger PL community.

From our survey does seem like some aspect-oriented language designers choose separation of concerns (obliviousness) as a goal that they want to have perfectly, and are willing to sacrifice other goals to it. The interesting thing to me was that many of the aspect-oriented language designs we surveyed in the paper, while preserving obliviousness, were willing to give up some on quantification in order to achieve other goals. In this sense they take obliviousness as non-negotiable. This seems fine and perhaps is the right way to go, but we wanted to try to get people in the aspect community and larger PL community talking more about the design space and possibilities in it. I hope that the paper helps that discussion a bit. We will be discussing it at the SPLAT 2007 workshop in March.