S.D.I.-Consulting BVBA

Abstraction and Meta Programming

Tue Nov 08 2005

This note is about abstraction. Abstraction meaning “ignoring the details, concentrating on the important mechanism”. A substantial software application consists of several important mechanisms. While writing code, a developer cannot keep the complete program in the brain.

There is too much detail in the application, the developer can only have a small fraction of it under his attention. Every good developer is an abstraction guru. Abstraction can be applied in several ways. There are many forms of abstraction it depends on how you look at the code. Take the Java Swing framework for instance. There are several interesting mechanisms in there which could be studied independently. The list is not exhaustive, I just mention a number these mechanisms.

All these mechanisms are hidden in the Java code (because I studied Swing; it is not specific for Java), you have to go out and hunt for them, it is like going on a safari. In stead of wild animal spotting you have to do some tough concept spotting.

The “art of programming” could be called “the art of making abstraction” as well. The art of taking different viewpoints and looking at/working out a separate aspect of the application. Is this supported in the current mainstream programming languages? I will coin this property “abstract-ability”. The sad answer to the question is “no”.

There are two languages I can come up with in which you can do this. First the LISP family of languages, and secondly RUBY. I am sure there are more languages that can do this, they will have the same characteristics. How come that the most important part of programming is barely mentioned? Most of the mainstream programming languages contain mechanisms to do some forms of abstraction e.g. procedural abstraction, data abstraction and so on, but this is not sufficient. Most of the time there is not a “mechanism abstraction” or a “pattern abstraction”.

I suspect the reason is that it is not well understood what it needs to write a program, how the process really works. Secondly, programming languages are often designed in a bottom-up way, the language designer makes a list of features that should be in the language and creates a programming language around these features. Whereas a programming language is the programmer’s ultimate tool to express himself, not a collection of features.

Looking at the above examples, we can see that there is a strong tendency and a need for an abstraction like mechanism. The techniques in the list (I don’t doubt there are more incarnations out there) are not sufficient because they are add-ons to the language, tricks done on the source or on the byte code. What I want is a programming language that lets us express code patterns.

What do I need? I want to be able to describe a pattern of code, an aspect, separate from other mechanisms. Secondly I need to integrate this code in different situations, or applications if you like. In short

  1. Describe the aspect in real working code, not an external substitute like a diagram.
  2. Reuse this abstract pattern where we need it. The pattern could be customized in different applications.

For the first point we need enough language syntax to describe the aspect. For the second point we need meta programming to integrate the patterns in any way we like. As a consequence, languages lacking meta programming are not sufficient.

I know it can be done in LISP. For a reason unknown to me Lisp is not used very often in current business applications; but it is a fact that Lisp provides abstract-ability. I will clarify the idea using RUBY in stead, I suspect that Ruby is more approachable than Lisp for many people. Let’s take the Observer pattern as described in GOF p. 293. I will not talk about the pattern here, you can look it up or search the web.

First we describe the pattern components in Ruby. The pattern contains 3 key classes: (1) Subject, (2) Observer and (3) an Aspect. In short, the Observer is interested in the Subject and the Subject will send the Observer a change message, called an Aspect.

 1 class Subject  
 2   def initialize
 3     @observers = []
 4   end  
 5   def attach(observer)
 6     @observers << observer if not @observers.include? observer
 7   end  
 8   def detach(observer)
 9     @observers.delete(observer)
10   end  
11   def notify(aspect)
12     @observers.each {|observer| observer.update(aspect)}
13   end
14 end 
15 
16 class Observer  
17   def initialize(instance, methodName)
18     @instance = instance
19     @methodName = methodName
20   end  
21   def update(aspect)
22     @instance.send(@methodName, aspect)
23   end
24 end 
25 
26 class Aspect    
27   def initialize(subject)
28     @subject = subject
29   end
30 end 

So far so good, we could do this in almost any object-oriented language. Next we construct the helper procedures which we can use later on to apply the pattern. That is the hot stuff. We don’t want to repeat the above code each time we need it. once it is written and tested we want to apply it. Note that the Observer pattern is applied in a not so trivial way.

 1 def make_subject(name)
 2   subject_name = "@subject_#{name}"
 3   attach_name = "attach_#{name}"
 4   detach_name = "detach_#{name}"
 5   notify_name = "notify_#{name}"  
 6   eval %{  
 7   def #{notify_name} (aspect)
 8     #{subject_name} = Subject.new if not #{subject_name}
 9     #{subject_name}.notify(aspect)
10   end
11   def #{attach_name}(observer)
12   #{subject_name} = Subject.new if not #{subject_name}
13     #{subject_name}.attach(observer)
14   end
15   def #{detach_name}(observer)
16     #{subject_name} = Subject.new if not #{subject_name}
17     #{subject_name}.detach(observer)
18   end
19   }  
20 end 
21 
22 def make_observer(name, methodName)
23   observer_name = "@observer_#{name}"
24   observe_name = "observe_#{name}"
25   eval %{
26   def #{observe_name}
27     #{observer_name} = Observer.new(self, :#{methodName}) if not #{observer_name}
28   end
29   def observer_#{name}
30     #{observer_name}
31   end
32   }    
33 end 
34 
35 def attach_observer(name, subject, observer)
36   observer_name="observer_#{name}"
37   observe_name="observe_#{name}"
38   attach_name = "attach_#{name}"
39   observer.send(observe_name)
40   subject.send(attach_name, observer.send(observer_name))
41 end 
42 
43 def detach_observer(name, subject, observer)
44   observer_name="observer_#{name}"
45   observe_name="observe_#{name}"
46   detach_name = "detach_#{name}"  
47   subject.send(detach_name, observer.send(observer_name))
48 end 

Finally an example of how the code above is used. This is the most important part of what I am trying to explain. I have a model class MyModel which can provide several subjects to interested parties. Note that the model incorporates the Subject in two different roles. If the Observer pattern were applied using inheritance, this would be impossible. Furthermore, inheriting from the Subject class would prevent us to inherit from another class. The only way we could achieve this in Java would be to manually replicate the code generated by the meta program. But then we loose the explicit application of the pattern, we might overlook the pattern when reading the code. In the code below, the mechanism is stated explicitly.

1 class MyModel
2   make_subject :tick
3   make_subject :alarm
4 end

We have two Observer classes, just to show that the Observer pattern can be applied in more complex ways. Again, we see an explicit statement that transforms the class into an Observer class.

 1 class MyClockComponent
 2   make_observer :tick, :processTick
 3   def processTick(aspect)
 4     puts "tick()"
 5   end  
 6 end 
 7 
 8 class MyAlertManager
 9   make_observer :alarm, :processAlarm
10   def processAlarm(aspect)
11     puts "alarm()"
12   end
13 end

Finally some invocations of the pattern instance, just to show that although we are working with patterns we can actually execute this stuff.

 1 subject = MyModel.new
 2 observer1 = MyClockComponent.new
 3 observer2 = MyAlertManager.new 
 4 
 5 attach_observer(:tick, subject, observer1)
 6 attach_observer(:alarm, subject, observer2) 
 7 
 8 aspect = Aspect.new(subject)
 9 subject.notify_tick(aspect)
10 subject.notify_alarm(aspect)
11 subject.notify_tick(aspect)
12 subject.notify_tick(aspect)

 

Conclusion

Once more this little thought experiment points towards the use of meta programming. It will play a major role in the future. It is not yet clear which form it will take. Ruby is an interesting language, it provides interesting constructs not in a feature oriented way but in a way that really assists developers to express programs. Ruby supports the meta programming way of working.

References: