Pure Aspect-Oriented Program: an example

H

Hung Jung Lu

Hi,

I have been looking into AOP (Aspect-Oriented Programming) for
sometime, now. I frankly don't like the syntax of any of the
approaches I have seen so far. I am kind playing around with some
ideas, and maybe write up an article later.

AOP is not just buzzword. It's not just callback, it's not just Ruby's
MixIn, it's not just Python's metaclass, it's not just C++'s template.
AOP can be implemented/considered as a subset of metaprogramming... an
important subset that stands on its own. AOP deserves its name,
because one really can think and program in "aspects" instead of
"objects". That being said, I have never seen an example of purely
aspect-based program. So, I thought I'd write up one.

Here it is, and let's get some feedback.

thanks,

Hung Jung

--------------------

Suppose you have to implement a checking account that allows
withdrawal of money. Your first simple implementation may look like:

class Account {
real balance
method withdraw(amount) {
this.balance = this.balance – amount
}
}

A program that withdraws money may look like:

account = new Account()
...
amount = 100
account.withdraw(amount)

In order to simplify the discussion, in the following I will omit all
initialization code details, and assume that variable values have been
properly assigned elsewhere. Also notice that everything is in
pseudocode, I am not using any particular language as base.

After you have implemented the program, your bank manager comes and
tells you there are constraints to the withdrawal of money. For
instance, an account maybe disabled. In OOP, you would modify your
Account class to:

class Account {
real balance
bool enabled
method withdraw(amount) {
if not this.enabled
raise AccountDisabledException
this.balance = this.balance – amount
}
}

Now, stop thinking in OOP. Let us try to think in AOP.

//---------------------------------------------------
class Account {
real balance
method withdraw(amount):
this.balance = this.balance – amount
}

//---------------------------------------------------
aspect AccountStatus {
bool enabled
codeblock check_status {
if not this.enabled:
raise AccountDisabledException
}
method withdraw(...) {
this.check_status
this.withdraw.code
}
}

//---------------------------------------------------
endow Account with AccountStatus


The general idea is to have the concerns implemented outside the
object. We have added a new feature to the account (namely, the
account status,) without having to tweak the original code of the
account. Notice that we have three parts to the above code: a class
definition, an aspect definition, and a final part to endow the aspect
to the class. The last part is also known as the "aspect weaver".

Let us proceed to the next step. Say, there is a maximum daily
withdrawal limit.

//---------------------------------------------------
class Account {
real balance
method withdraw(amount) {
this.balance = this.balance – amount
}
}

//---------------------------------------------------
aspect AccountStatus {
bool enabled
codeblock check_status {
if not this.enabled:
raise AccountDisabledException
}
method withdraw(...) {
this.check_status
this.withdraw.code
}
}

//---------------------------------------------------
aspect WithdrawalLimit {
real daily_limit
real withdrawn_today
codeblock check_and_update_withdraw {
new_withdrawn_today = this.withdrawn_today + amount
if new_withdrawn_today > this.daily_limit:
raise WithdrawalLimitExceededException
&inner_code
this.withdrawn_today = new_withdrawn_today
}
method withdraw(...) {
this.check_and_update_withdraw {
...
&inner_code = this.withdraw.code
...
}
}
}

//---------------------------------------------------
endow Account with AccountStatus
endow Account with WithdrawalLimit

Notice the usage of a hook (also known as pointcut): &inner_code. In
general, a code block or a method can contain one or many hooks. Hooks
allow future enhancement of code. There are two implicit hooks: the
before and the after hooks. But for readability of AOP code, in our
approach we treat named hooks very differently from implicit hooks.
Ruby users should notice that a code block here can contain multiple
hooks, and that the direction of hooking process is kind of opposite
to Ruby's MixIn.

The above way of programming of course takes longer to write. But, the
advantage is in the degree of decoupling (or "separation of concerns")
that is achieved. Say, one day, we want to eliminate the feature on
withdraw limit, it is as simple as commenting out one single line of
code:

endow Account with AccountStatus
//endow Account with WithdrawalLimit

The same is true if one day we want to eliminate the account status
feature:

//endow Account with AccountStatus
endow Account with WithdrawalLimit

Also, once the concerns are separated, it is easier to modify the
aspects individually.

A careful reader would point out that we may want to check the account
status before checking withdrawal limit. Notice also that we have
applied two aspects in sequence. But we can pre-compose two aspects
into one, and apply the composed aspect just once.

//---------------------------------------------------
aspect AccountAspects inherits AccountStatus, WithdrawalLimit {
method withdraw(...) {
this.check_status
this.check_and_update_withdraw {
...
&inner_code = this.withdraw.code
...
}
}
}
//---------------------------------------------------
endow Account with AccountAspects

At this point you may say: "Hey! We started with a non-trivial class…
but we could have started with an empty class, and endow the
balance-calculating feature as an aspect, too." Bingo! Now you are
thinking in AOP.

//---------------------------------------------------
class Account {
}
//---------------------------------------------------
aspect BalanceKeeping {
real balance
method withdraw(amount) {
this.balance = this.balance – amount
}
}

//---------------------------------------------------
aspect AccountStatus {
bool enabled
codeblock check_status {
if not this.enabled:
raise AccountDisabledException
}
method withdraw(...) { // this meta-method is overriden later
this.check_status
this.withdraw.code
}
}

//---------------------------------------------------
aspect WithdrawalLimit {
real daily_limit
real withdrawn_today
codeblock check_and_update_withdraw {
new_withdrawn_today = this.withdrawn_today + amount
if new_withdrawn_today > this.daily_limit:
raise WithdrawalLimitExceededException
&inner_code
this.withdrawn_today = new_withdrawn_today
}
method withdraw(...) { // this meta-method is overriden later
check_and_update_withdraw {
...
&inner_code = this.withdraw.code
...
}
}
}

//---------------------------------------------------
aspect AccountAspects: inherits BalanceKeeping,
AccountStatus,
WithdrawalLimit {
method withdraw(...) {
check_status
check_and_update_withdraw { // this meta-method overrides
...
&inner_code = this.withdraw.code
...
}
}
}
//---------------------------------------------------
endow Account with AccountAspects
print Account.codeString()

Notice that we have started with a bare class with no attributes. All
the features of the class Account have been implemented by aspects,
instead of interfaces or base classes. You may complain: the code is
now too hard to read and follow. But that actually is a problem of IDE
(Integrated Development Environment). For more advanced IDEs, editing
aspects actually should not be bad at all. It's almost like editing
the header and footer information in a Microsoft Word document. At any
rate, the last statement above would print out the code for the
"aspected class", which may look something like:

class Account {
real balance
bool enabled
real daily_limit
real withdrawn_today
method withdraw(amount) {
if not this.enabled:
raise AccountDisabledException
new_withdrawn_today = this.withdrawn_today + amount
if new_withdrawn_today > this.daily_limit:
raise WithdrawalLimitExceededException
this.balance = this.balance – amount
this.withdrawn_today = new_withdrawn_today
}
}

A good IDE/compiler/debugger can provide additional information on
where each code line or code block comes from, hence making debugging
and editing a snap. But we don't need to get into that discussion,
now.

What have we learned? We have learned that:

1. We can write a purely-AOP program.
2. Aspect inheritance can be used to compose derived aspects.
3. A new type of object: "codeblock", becomes the fundamental building
block of programs. So, in general we have to consider the (data,
codeblock, method) trio when building classes or programs. A method
consists of its "header/prototype" and a reference to its codeblock.
Also, in a real program, most codeblocks would be anonymous.
4. Despite of its innocent look, the code specifying an aspect
contains disguised meta-programming instructions.

Codeblock-based AOP actually is more suitable in programming languages
powered with meta-programming features. Also, (data, codeblock,
method) are all supposed to be virtual and overridable. Data
overriding does not do anything, if the data is already present.
Codeblock overriding does not present problem. The main problem is
with method overriding. If the new method refers to the existing
method, there is a problem as how to deal with the names and who will
hold reference to the old method once the new method is in place. For
the AOP practitioners, this means that the "before" and "after"
advices can be implemented more easily, where as the "around" advice
is trickier. A possible solution is to "decorate" the name of the old
implementation. Here I only present a possible syntax of the "around"
advice.

class C {
method f() {
return 3
}
}

aspect A {
method f_before_A = f
method f(...) {
print 'begin around-advice'
result = this.f_before_A()
print 'end around-advice'
return result
}
}

endow C with A
c = new C()
print c.f()

//------ output
begin around-advice
end around-advice
3

One thing I have not mentioned is pattern matching for method names.
An aspect may affect many or all the methods in a class. In that case,
the name of the methods should be listed, or wildcards (possibly
regular expressions) must used to pattern-search for them. Possible
syntax variations are:

aspect A {
method <f>(...) for f in f1, f2, f3 {
}
}

aspect B {
method <f>(...) for f matching 'f*' {
}
}

Another thing that I have not mentioned is that aspects can be endowed
to different classes. For instance, if later we have checking accounts
and savings accounts, we can apply the same aspect to both classes.
That's what other people refer to as "cross-cutting".

.... That's all for now.
 
R

Richie Hindle

[Hung Jung]
I have never seen an example of purely
aspect-based program. So, I thought I'd write up one.
Here it is, and let's get some feedback.

Fantastic! That's the best introduction to the concepts of AOP I've seen.
Many thanks for a very clear and thought-provoking article.

Now please implement it for Python, including your proposed IDE. :cool:
 
T

Taliesson Wang

Yes, AOP is more like a metaprogramming.
It supplies a vertical-cut reconstrcution way to OOP.
I'm now writing something about rule-base system & meta-analysis.And hope
this will do something for AOP.
 
H

Hung Jung Lu

Richie Hindle said:
Fantastic! That's the best introduction to the concepts of AOP I've seen.
Many thanks for a very clear and thought-provoking article.

Now please implement it for Python, including your proposed IDE. :cool:

Yeah, right. :)

I do realize that people are converging in their ideas. AOP is
becoming more and more clear with each passing day. That's really
exciting. But I believe things have not yet finalized, and I am not a
"marketoid" to go out and sell half-baked ideas. :)

Java folks have economical resources, but their language puts so much
handcuff on them that AOP there is kind of hard to use. Ruby is
perhaps the language that's easiest to accomodate, and Python comes a
close second. As for IDEs, you can bet Java folks will have something
before anyone else. Although I am not sure why people'd keep investing
effort into such a rigid language.

See for instance:

http://www.hpcc.gov/iwg/sdp/vanderb...egor_kiczales_aspect_oriented_programming.pdf

where people are focusing a bit on IDE already, though theirs is not
nearly as good as my proposed one. :)

Hung Jung
 
J

Jay O'Connor

Yeah, right. :)

I do realize that people are converging in their ideas. AOP is
becoming more and more clear with each passing day. That's really
exciting. But I believe things have not yet finalized, and I am not a
"marketoid" to go out and sell half-baked ideas. :)

Java folks have economical resources, but their language puts so much
handcuff on them that AOP there is kind of hard to use. Ruby is
perhaps the language that's easiest to accomodate, and Python comes a
close second. As for IDEs, you can bet Java folks will have something
before anyone else. Although I am not sure why people'd keep investing
effort into such a rigid language.

AFAIK, Aspect Oriented Programming is already available in a few
Smalltalk dialects, possibly even support within the IDEs
 
H

Hung Jung Lu

(e-mail address removed) (Hung Jung Lu) wrote in message
Here I only present a possible syntax of the "around" advice.
...
aspect A {
method f_before_A = f
method f(...) {
print 'begin around-advice'
result = this.f_before_A()
print 'end around-advice'
return result
}
}

(I changed comp.lang.java to comp.lang.java.programmer)

Yikes, that's awfully bad. It violates functional programming
philosophy of the meta-methods.

I finally figured out a cleaner syntax for the "around" advice, using
@-decorated names and method hooks.

class M {
method f(x) {
print 'multiply by 2'
result = 2 * x
print 'result =', result
return result
}
}

aspect A {
codeblock f_with_A {
print 'post-multiply by 3'
result = 3 * &f(...)
print 'result =', result
return result
}
method f@A(...) {
this.f_with_A {
&f = this.f@
}
}
}

aspect B {
codeblock f_with_B {
print 'pre-multiply by 4'
x = 4 * x
result = &f(...)
print 'result =', result
return result
}
method f@B(...) {
this.f_with_B {
&f = this.f@
}
}
}

aspect C inherits A, B {
method f@C(...) {
this.f_with_B {
&f = this.f@A
}
}
}

endow M with C
m = new M()
x = 1
print 'input =', x
print m.f(x)

//------ output
input = 1
pre-multiply by 4
post-multiply by 3
multiply by 2
result = 8
result = 8
result = 24

Notice that I have used a method hook &f, not a codeblock hook. Notice
also the usage of (...) for signature decoupling.

Aspect C will create the "@-decorated" methods f@A, f@B and f@C for
class M. The original method can always be accessed as f@(x). When
f(x) is accessed without name decoration, the latest @-decorated
implemention is used. However, in order to avoid meta-method
ambuiguities in multiple-aspect inheritance, the hooking process
should always be done with explicit @-decorated names. If you really
don't like a particular meta-method, you can always override it. So I
think usage of explicit @-decorated name inside a meta-method should
be OK.

I guess the @-decoration could be used in all meta-methods, that is,
even for the cases of "before" and "after" advices.

Hung Jung
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,521
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top