Advanced Announcements, part 5
Rich and Travis have already been discussing declarations and reflection. This post is not as much a user's guide as an explanation why the current incarnation of the framework doesn't commit very much to that idea. I can say right away that I am not brimming with excitement regarding declarations. Nor am I rejecting the idea outright.
I'll start with some definitions and clarifications so we don't entangle ourselves in our own misinterpretations.
One thing we talk about is reflection. Loosely speaking, reflection is the ability of our Smalltalk image to know and/or modify its structure. In our neck of the woods, reflection is universally considered to be a Very Good Thing.
The other thing are declarations. One motif that rather bothered me in the discussion was the apparent confusion between declarations and reflection, with an implication that declarations are needed because they provide reflection and reflection is good. Declarations and reflection are entirely different things. You can have one and not the other. Declarations can support reflection, but they are neither required nor automatically enable it. Consider that C++ and similar languages are very declaration-heavy and reflection-light, while Smalltalk is the other way around.
We can fairly accurately find out what messages methods of a particular class send without having the class declare each message it sends. What we find is not guaranteed to be 100% accurate, but for most tasks in tools 90% is good enough. Enough to say that requiring mandatory declarations of all sent messages so the tools could gain the last 10% is not a very attractive proposition, at least not within our laid-back Smalltalk culture.
To avoid confusion, it's better to separate reflection into two kinds: introspective reflection, when you muck around with code and figure out what it does, and declarative reflection when you know what the code does because declaration restricts what it could do. (Yes, I've just made these terms up).
The important difference between these is that introspective reflection follows the code while declarations want the code to follow them. This once again shows why reflection and declarations are not the same. The primary goal of a declaration is limiting what is possible in the code, and reflection is only a byproduct of that. In fact, declarative reflection is not true reflection at all--you know your code won't do what declaration does not allow, but you don't know how much it does of what is allowed. You still need introspective reflection to know that.
Declarations establish a law for code to follow, and there is no law without law enforcement. Unless you ensure the code only does what the declarations say, any "reflection" based on the declarations is only an educated guess. So, reflection might not go hand-in-hand with declarations, but enforcement does.
Enforcement can be done at compile time and runtime. Compile-time enforcement is far superior. It's more user-friendly because it points our errors as soon as they are made. It's also more reliable because it catches all errors, while runtime errors can go unnoticed if the particular piece of code runs rarely. This is why even for message sends, the sent selector is verified to at least exist at compile time, with a warning if it doesn't.
With all the above combined, I think I know what Travis meant when he said about using introspective reflection for finding announcements, "This seemed like a cool idea, because, it's so... I don't know what the right word is." It's so much more in the spirit of Smalltalk to let the code be free and follow it when needed, rather than establish artificial limits at a high level, with whatever good intentions. Consider how the existing declarations (such as variable, import, pragma) restrict the code only in the most basic ground rules, usually enforced at compile time.
This is why I am not too keen on embracing announcement declarations outright. They feel alien. They are too high up the food chain. Consider the parallel with Exceptions. There are no declarations of exceptions a class can signal. Do we need them? I'm sure someone could come up with a reason for that. Are we doing well without them? There's the rub.
If Exceptions are not a good example as far as declarations are concerned, what about #eventsTriggered in triggerEvent? Here is why eventsTriggered are apparently intended to solve an unrelated problem, which Announcements solve much better by different means.
Let's start with this. Assuming a class has a declaration of announced things of some sort, enforced at runtime, when exactly should it be enforced? The #announce: method is one obvious spot, since otherwise we could announce things we claim we don't announce. What about when accepting subscriptions--should it be ok to subscribe to something an object can't announce? In other words, what precisely does an announcement declaration restrict: only announcements, or both announcements and subscriptions?
Regardless of the answer, what triggerEvent provides is off the mark. It has something called event checking, turned off by default in Object. A subclass can turn it on and add #eventsTriggered declarations. However, a closer look reveals that what is checked against those declarations are actually the "when:..." subscription messages, while triggerEvent: won't balk at anything. So, despite the "eventsTriggered" name, instances can trigger anything at all--it's just that they won't accept subscriptions to it!
Obviously, event checking in triggerEvent isn't concerned with any high-level principles like "declarations should limit and describe what the code can do". It must have been implemented ad-hoc to address some immediate practical needs. And I think the main of those was protecting against typos. When events are symbols, it's easy to mistype one, subscribe to an unexistent event and then wonder why the event is apparently not being triggered. So the true purpose of #eventsTriggered, despite the name, is not to list the events a class triggers, but simply list some symbols the class knows are valid event names it presumably triggers.
Now, to Announcements.
If the true purpose of #eventsTriggered appears to be protection against typos, Announcements have it. Only better. If you mistype an announcement name you get an error at compile time, and the error checker can event find the correct name for you.
With this out of the way and the counter-example of Exceptions, I hope it's getting clear why I am not entirely convinced declarations are the way to go. I believe we simply don't have enough collective experience using them to come to any conclusions right now. Now is the time to let a hundred flowers bloom to get that experience, and not expect an answer in a month--and probably not in a year.
This is why all the Announcements proper provides is a hook for runtime enforcement of both subscriptions and announcements. All announcements and all subscription requests need to pass a class check implemented by #mayAnnounce: method of the announcer. The method accepts the class being announced or subscribed to as the argument and should answer false if the class is not supported.
However, there is no "official" way in Object of declaring the supported announcements, and the default implementation of #mayAnnounce: in Object answers true to everything.
Having said this, I published to the public repository System-Announcements-Extras package with Announcer class implementing announcement declarations--as an example or a superclass to subsclass from for those willing to experiment. As I said, I don't reject the idea outright.
This is the lay of the land for the near future.
Comments
Re: Advanced Announcements, part 5
[ Rich Demers] December 8, 2005 13:53:19.693
Comment by Rich Demers
I get Travis' point about declarations, and I am no more in favor of enforcing static declarations (of anything) than any other Smalltalker. But there is a fundamental requirement that is being ignored here. How does a programmer know what announcements a class of objects can trigger?
And then there is the requirement that it be possible for tools (like SmalltalkDoc and application generators) to be able to acquire this information programmatically, which is where reflection comes in.
Notice that I have said nothing about how this information is provided. If it can be done dynamically, along the lines Travis suggested, all the better. If unenforced static declarations are needed, then so be it. Personally, I don't care how, but SmalltalkDoc needs to be able to distinguish between the total set of announcements a class can trigger and the smaller set that its methods trigger.
[Vassili] December 8, 2005 20:24:55.469
That requirement is not being ignored. It is being considered a part of the bigger picture. What I care is that the cost of satisfying that requirement does not exceed the benefit. Many programming languages have been screwed up by caring about abstract requirements rather than the users. Somehow that bondage and discipline focus on protecting stupid programmers from themselves or catering to their purported needs without concern of what they will have to pay is very popular in both the industry and in academia.
So once again, my point is not that we don't need to know what announcements a class signals. My points is that I am totally unconvinced that declarations similar to eventsTriggered give you the most bang for the buck in that. I am including the hooks to enable them to experiment, but not declarations as such. In fact, the more I think about it the more I'm leaning to the viewpoint that declarations aren't the solution.
I talked about "declarative reflection" above, but in fact I want to take that back. Declarations are the opposite of reflection. A declaration that the class announces A, B and C actually means that the class doesn't announce anything else. A class might not be announcing A, B and C either. You say that the problem with external documentation is that it gets out of date, but how is external documentation fundamentally different from an external declaration? You might have written that A, B, C declaration months ago, and since then the code has changed so that A is no longer announced. The class doesn't announce A but the declaration says so. The user will subscribe to and won't hear anything back.
This is why I believe more and more that only real reflection based on inspecting the code is the way to go in knowing what gets announced. Code doesn't lie. Yes, reflection might not get all the uses 100% right but in tools anything over 90% is usually good enough. Better than maintaining external declarations.
Agree
[ Terry] December 9, 2005 8:33:02.600
Comment by Terry
Vassili
I agree with your comment, very much so.
However, if you want to use reflection to find all the announcements announced by a class, how can you be guaranteed to find them all when there is code that does this;
In this situation you are asking the DomainObject class for its announcements, but one of the methods that initiates an announcement is in a completely different class, appclass. I don't see how you can find this by reflection.
Re: Advanced Announcements, part 5
[ Rich Demers] December 9, 2005 9:29:34.655
Comment by Rich Demers
Vassili, you've convinced me! Declarations aren't the solution. In truth, I never really cared what solution is used to meet these requirements. Travis suggested that the methods of a class be analyzed to determine which announcements it triggers, but a better algorithm is needed that actually looks at the method parse trees and and the Announcement class hierarchy.
This still leaves the problem that announcements can be triggered by a delegate object, as in
It probably wouldn't be possible to detect delegate triggers and relate them back to the class of objectX. But as you suggest, a 100% solution isn't necessary. Besides, a simple coding convention solves this problem readily. Have the delegate code
where #announceChanged is a method of objectX
I have always put trigger messages in their own methods, anyway, so that they can be gathered in a single "event triggering" protocol, and easily found in the System Browser. I will do the same with announcements. And apply the same practice to "announcement subscribing," "announcement handling," etc.