Abstraction & Meta Programming
8 Nov 2005This 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.
- Paint mechanism (dirty region management, propagation, scheduling and redraw).
- Component structure (model, component, ui-delegate roles).
- Event notifications (observer patterns).
- Keystroke handling.
- Focus handling.
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.
- UML diagrams. These are used to highlight important mechanisms of an application. It is not expressed in the language itself, it is an external representation. I would like to see more expressiveness in the programming language itself.
- Literate programming. Invented by Prof. Dr. Knuth. This makes it possible to talk about a specific mechanism in a separate paragraph or chapter, the actual code is included, the complete application is generated from the literate program. This is a very nice mechanism, but the problem is that it is not the code itself that contains the concepts. Knuth would argue that the literate program is the real source, not the generated application.
- Aspect oriented programming. Yes I mentioned aspects before and aspect oriented programming is a step in the right direction. The problem remains that in Java for example it is kind of a trick done on the byte codes. I would like to see a similar mechanism which is integrated in the Java language itself.
- Multiple inheritance. It can come in handy, but it is not sufficient either. Sometimes you must be able to fulfill the same functionality in different roles, you cannot inherit more than once from the same super class to accomplish this.
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
- Describe the aspect in real working code, not an external substitute like a diagram.
- 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:
- I talked about meta programming in previous meme's: "Abstraction in Java and Lisp", "The Next Paradigm".
- I mentioned Ruby in "Ready for Ruby".