Difference between revisions of "Guide:Aspects"

From IokeWiki
Jump to: navigation, search
(correcting some mistakes)
m (Reverted edits by Icotime (Talk) to last version by Purdeaandrei)
 
(One intermediate revision by one other user not shown)
(No difference)

Latest revision as of 16:28, 24 November 2010

Aspects

In many circumstances you might want to do several different things in a specific method. That generally goes against good software engineering, that says every piece of code should only have one responsibility. Aspect oriented programming tackles this by allowing you to slice code along a different dimension compared to the regular object oriented paradigm. Ioke has a small version of this, that allow you to add advice to cells. This advice can be before, after or around. All of them will get access to the arguments given in the original message, but after-advice also gets access to the value returned from the main cell. Finally, around-advice also gets the responsibility to invoke the original cell, so an around-advice could choose to not invoke it at all, or it could invoke it with different arguments, or maybe invoke it several times.

Ioke currently only allows you to add or remove advice. To be able to remove advice, you need to name it when adding it. Unnamed advice can not be removed. Advice can be wrapped. It will be applied in an outside-in approach, where the most recently added advice will execute first.

The return value of before and after advice doesn't matter, but the return value of around-advice will be interpreted as the new return value of the message send.

To manipulate advice in Ioke, you need to describe what to manipulate. This description will return a Pointcut, that can then be used to inject advice at that point. Pointcuts can be defined in several ways. The easiest is to define it in terms of a cell name. Using this approach is the only way to define advice for non-existent cells.

To create a Pointcut, call before, after or around on the receiver where the Pointcut should belong. The arguments specify what should be matched by the pointcut. You can see some examples of pointcuts here:

; matches cells foo and bar
Text before(:foo, :bar)

X = Origin mimic
; will not match anything
X around(matching: :anyFromSelf)

X foo = 123
; will now match foo
X around(matching: :anyFromSelf)

; any cell name matching the regexp
X after(matching: #/foo/)

; matches any at all, except foo
X before(matching: :any, except: :foo)
; matches any at all, except anything that matches foo
X before(matching: :any, except: #/foo/)

; use a block for matching
X around(matching: fn(x, x == :blurg))

; use a list to provide alternatives
X before(matching: [#/foo/, #/bar/])

As you can see from these examples, the pointcuts can be fairly advanced and specific in what they match. The option to send in a block makes it possible to check any property while matching.

OK, once you have a Pointcut, there are three different methods you can call. These are <<, add, and remove!. The first one adds an unnamed advice, the second adds a named advice and the third one removes a named advice. Advice is any object that can be activated or called, so a method, a block, a macro or a syntax is fine.

It's time to see what it looks like to add advice. Before-advice is the easiest kind. The important thing to remember is that the code will get the same arguments as the original call, which means it will also signal an error if the arguments doesn't match.

X before(:foo) << method(x, "got #{x}" println)
X before(matching: :anyFromSelf) << macro(
  "called #{call message name}" println)

This code doesn't do anything strange at all.

Next up we have after-advice. The only difference here is that after-advice automatically gets a cell that is set to aspectResult, that can be used inside the method.

X after(:foo) << method(+args,
  "foo resulted in: #{aspectResult}" println)

Remember that the result value of before and after advice doesn't count. It is thrown away. But these methods can still affect things by side effects. They can also be used for validation. A condition signalled inside of an advice would have the same effect as if done in the method itself - namely interrupting the flow of control.

The final advice is around advice. These are different in two ways. First, they get access to a cell called aspectCall, which can be used to invoke the real cell (and the next chain of advice, of course). The second difference is that the result of the around advice will be the return value from the cell. So, you can imagine the around-advice executing instead of the original code. If you forget to invoke it, the original cell won't be invoked at all.

X around(:non_existant) << method(
  42)

X around(:foo) << method(arg1, arg2,
  "before" println
  res = aspectCall(arg2, arg1)
  "got: #{res}" println
  42)

The first piece of code show that you can actually use an around advice around something that doesn't exist. But if you do call aspectCall inside of it, that will generate a NoSuchCell condition, of course. In the second example we first log some information, then invoke the original code with switched argument order, saves away the result, prints the result, and finally returns 42. There you can see most of the things that can be done inside of an around macro.

The aspect system in Ioke is implemented in Ioke itself, and is fairly small at the moment. The guiding principle behind it is that it shouldn't have an impact on code that doesn't use it. It is a highly useful feature that makes it possible to decompose code substantially. As an example of a common place for aspect usage is initialization. This is defined as an around-advice that looks like this:

Origin around(:mimic) << method(+rest, +:krest, 
  newMimic = aspectCall
  if(newMimic cell?(:initialize),
    newMimic initialize(*rest, *krest))
  newMimic)

Note first that the around advice takes any kind of arguments, but doesn't send them along to mimic. Instead it checks if there is an initialize-cell, and in that case invokes it with the arguments given to the mimic call. It finally returns the original result.