S.D.I.-Consulting BVBA

Menu:

Abstraction & Meta Programming

8 Nov 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 "abstractability". 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 abstractability. 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.

class Subject  
  def initialize
    @observers = []
  end  
  def attach(observer)
    @observers << observer if not @observers.include? observer
  end  
  def detach(observer)
    @observers.delete(observer)
  end  
  def notify(aspect)
    @observers.each {|observer| observer.update(aspect)}
  end
end 

class Observer  
  def initialize(instance, methodName)
    @instance = instance
    @methodName = methodName
  end  
  def update(aspect)
    @instance.send(@methodName, aspect)
  end
end 

class Aspect    
  def initialize(subject)
    @subject = subject
  end
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.

def make_subject(name)
  subject_name = "@subject_#{name}"
  attach_name = "attach_#{name}"
  detach_name = "detach_#{name}"
  notify_name = "notify_#{name}"  
  eval %{  
  def #{notify_name} (aspect)
    #{subject_name} = Subject.new if not #{subject_name}
    #{subject_name}.notify(aspect)
  end
  def #{attach_name}(observer)
  #{subject_name} = Subject.new if not #{subject_name}
    #{subject_name}.attach(observer)
  end
  def #{detach_name}(observer)
    #{subject_name} = Subject.new if not #{subject_name}
    #{subject_name}.detach(observer)
  end
  }  
end 

def make_observer(name, methodName)
  observer_name = "@observer_#{name}"
  observe_name = "observe_#{name}"
  eval %{
  def #{observe_name}
    #{observer_name} = Observer.new(self, :#{methodName}) if not #{observer_name}
  end
  def observer_#{name}
    #{observer_name}
  end
  }    
end 

def attach_observer(name, subject, observer)
  observer_name="observer_#{name}"
  observe_name="observe_#{name}"
  attach_name = "attach_#{name}"
  observer.send(observe_name)
  subject.send(attach_name, observer.send(observer_name))
end 

def detach_observer(name, subject, observer)
  observer_name="observer_#{name}"
  observe_name="observe_#{name}"
  detach_name = "detach_#{name}"  
  subject.send(detach_name, observer.send(observer_name))
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.

class MyModel
  make_subject :tick
  make_subject :alarm
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.

class MyClockComponent
  make_observer :tick, :processTick
  def processTick(aspect)
    puts "tick()"
  end  
end 

class MyAlertManager
  make_observer :alarm, :processAlarm
  def processAlarm(aspect)
    puts "alarm()"
  end
end

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

subject = MyModel.new
observer1 = MyClockComponent.new
observer2 = MyAlertManager.new 

attach_observer(:tick, subject, observer1)
attach_observer(:alarm, subject, observer2) 

aspect = Aspect.new(subject)
subject.notify_tick(aspect)
subject.notify_alarm(aspect)
subject.notify_tick(aspect)
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: