Sunday, January 28, 2007

Some Self Philosophy

From the Self 4.1 Programmer's Reference, Chapter 4, A Guide to Programming Style:
In short, to maximize the opportunities for code reuse, the programmer should:
  • avoid reflection when possible,
  • avoid depending on object identity except as a hint, and
  • use mirrors to make reflection explicit when it is necessary.

This is the summary to a portion of the chapter entitled, "Behaviorism versus Reflection". It's best explained in this three paragraphs:
One of the central principles of SELF is that an object is completely defined by its behavior: that is, how it responds to messages. This idea, which is sometimes called behaviorism, allows one object to be substituted for another without ill effect—provided, of course, that the new object’s behavior is similar enough to the old object’s behavior. For example, a program that plots points in a plane should not care whether the points being plotted are represented internally in cartesian or polar coordinates as long as their external behavior is the same. Another example arises in program animation. One way to animate a sorting algorithm is to replace the collection being sorted with an object that behaves like the original collection but, as a side effect, updates a picture of itself on the screen each time two elements are swapped. behaviorism makes it easier to extend and reuse programs, perhaps even in ways that were not anticipated by the program’s author.

It is possible, however, to write non-behavioral programs in SELF. For example, a program that examines and manipulates the slots of an object directly, rather than via messages, is not behavioral since it is sensitive to the internal representation of the object. Such programs are called reflective, because they are reflecting on the objects and using them as data, rather than using the objects to represent something else in the world. Reflection is used to talk about an object rather that talking to it. In SELF, this is done with objects called mirrors. There are times when reflection is unavoidable. For example, the SELF programming environment is reflective, since its purpose is to let the programmer examine the structure of objects, an inherently reflective activity. Whenever possible, however, reflective techniques should be avoided as a matter of style, since a reflective program may fail if the internal structure of its objects changes. This places constraints on the situations in which the reflective program can be reused, limiting opportunities for reuse and making program evolution more difficult. Furthermore, reflective programs are not as amenable to automatic analysis tools such as application extractors or type inferencers.

Programs that depend on object identity are also reflective, although this may not be entirely obvious. For example, a program that tests to see if an object is identical to the object true may not behave as expected if the system is later extended to include fuzzy logic objects. Thus, like reflection, it is best to avoid using object identity. One exception to this guideline is worth mentioning. When testing to see if two collections are equal, observing that the collections are actually the same object can save a tedious element-by-element comparison. This trick is used in several places in the SELF world. Note, however, that object identity is used only as a hint; the correct result will still be computed, albeit more slowly, if the collections are equal but not identical.

Basically, the above is basically placing more value on "duck typing" (the new word for behaviorism) than reflection. I've always placed myself in the "behaviorist" camp and anyone that knows me rolls their eyes when I rip into my "@#$%^& not another data structures and controllers architecture!" It's because I place more value on the behavior than I do the data. It has a lot to do with my mentors enlightening me to the teachings of the brilliant Rebecca Wirfs-Brock (yes, she is still my hero).

Sorry for my digression, but why did I quote all of this? First off, I always thought of duck typing and reflection as tools in my bag. I have never thought about why I would pick one over the other. I do tend to pick non-reflective solutions where I can (It seems Joshua Bloch does the same from his Effective Java book as well). The reason being that most people understand behavior, but reflection is not so obvious.

OK, enough background, and on to my real point. This all got me thinking about why I've always been squeamish with reflective GUI, persistence, and rule engine frameworks. Now, before I begin, I love my OO-relational mapping tools and rules engines, but somehow they have always seemed like they were breaking encapsulation. And in fact, they are for great benefit (which outweighs the encapsulation violations). They are going underneath the covers of your objects and exposing them to a privileged few. Now, this gives us great power and takes a lot of things out of our hands. These frameworks do a lot of heavy lifting and breaking encapsulation has always seemed like a small price to pay. But, how could we do it without reflection? Ah, there's an interesting question, no?

Is this mental aerobics going to get us anywhere? I don't know. But, I bet the journey will be fun. So, what do we have in our arsenal right now? Well, off hand you can name the Memento pattern and we could have a simple hash table object that we get values in and out of the object. This could work for all of the frameworks I listed. But, it seems cumbersome and still prone to major changes in the object topology. We're still depending on data, just putting a common interface on an object. Perhaps, this is the point of "Mirrors" in Self is to provide this type of functionality, albeit consistently. It's also slow because we have to keep taking snapshots if we want to track changes (which means we have to ask for the data and then do compares). But, we could use the Observer pattern and trigger events on any change to an object at the end of some change transaction.

Another tool can be found in Allen Holub'sexcellent article about alternatives to getters/setters in GUIs using the Builder pattern. Now, the solution is very specific, but it's a turn on the Memento pattern. I like that fact that it's more about behavior where I have to send an object that understands the messages and reacts to those messages instead of being passive. But, it still seems the Memento pattern wins because it can be more generic (at its simplest: get(key) and put(key, value)).

I'll leave this article as a point to ponder. There might not be an answer. But, I think a pure behavior approach is more understandable and simple than a reflective one. If anyone has any thoughts, please send them to me. I'll post any further thoughts as I have them. I'm always thinking of ways to preserve encapsulation in my designs. Just remember, Alan Kay wished he had called object-oriented programming, "message-oriented programming". It enforces the black box nature of objects and strong communication semantics. It keeps designs simple and easily reasoned about. Now, don't get me wrong, I don't hate reflection. I just feel like we should always seek alternatives, like inheritance it holds awesome power. But, in the wrong context can cause maintenance issues. Just because you have a tool doesn't mean you have to use it. Besides, forcing constraints on yourself, can cause interesting thoughts on your future designs.

4 comments:

rpw said...

This is an interesting discussion. Are you familliar with Michael L. Perry's work on Automated dependency tracking?

It uses reflection at its core, but results in objects that are, IMHO, highly "behavioral". I believe it would be interesting to investigate the use of this approach in a duck typed language like Smalltalk or Self. Tweak has taken some steps in that direction, actually.

Eric said...

What papers, books, etc. would you recommend reading from Rebecca Wirfs-Brock? I'm just curious what you've read / been influenced by that you would recommend to someone else. (So we can be enlightened as well :))

mperry said...

Hi, this is Michael L. Perry, author of the referenced article "Automate Dependency Tracking".

I am very much a fan of behavioral modeling. I agree that behavior is much more important than data structure. However, I tend to apply this concept in strongly typed languages like Java and C#. I use interfaces to represent the set of behaviors that an object exhibits while hiding the unimportant details like the class that implements it.

In practice, I find that it is important to have the compiler do some of the checking for you. Not type-checking exactly, but behavior-availability-checking. I want to document clearly that I expect a parameter to provide a given set of behaviors. The compiler can help me to document that if I give that set of behaviors a name and make it an interface.

I'd also agree that reflection is something to be avoided. Although it certainly appears to, my dependency tracker does not use reflection. It simply stores a backward reference in global memory so that it can record dependency when it occurs. Since writing the article, I've ported the code to C# and made a number of improvements. Now it is thread-safe and embedded into a set of Windows Forms controls. You can download these controls from http://updatecontrols.net.

Great writing. Keep up the good work.

malcolmsparks said...

"These frameworks [(eg. persistence)] do a lot of heavy lifting and breaking encapsulation has always seemed like a small price to pay. But, how could we do it without reflection? Ah, there's an interesting question, no?"

I've often wondered whether it's possible to do ORM (Object Relational Mapping) without resorting to reflection, ie. just using good old design patterns. There's a lot to consider: there's lots of 'stuff' that ORM products give you, not least the tracking of which objects are transient and which are persisted. However, I'm drawn to the belief that it's possible to address ORM purely by convention (with some heavy use of design patterns) rather than through a product that utilizes reflection. A better OO designer than me would have to take up the challenge, but as you say, 'the journey will be fun'.

Excellent blog, btw. my first comment but have been reading it for a few months now. Keep up the excellent work!

Amazon