Tuesday, March 25, 2008
Smalltalk Announcement Experiences
In a previous post, I described how I was interested in trying out the Announcements Framework in Dolphin Smalltalk and seeing how it changed my code.
Having ported the code to Dolphin, I set about looking at places where I had used Symbolic events and seeing how I might make use of some of the features of Announcements.
I noticed a common pattern in my GUI components (Dolphin has a very interesting GUI compositional model that bears further discussion in a separate post because it really is quite elegant) where I was looking for specific events on my model in order to update a tree widget.
(Array with: aStoryProject backlog with: aStoryProject plan)
I decided to make two changes - the first being adding "," concatenation to my model (copying the idea that impressed me with AnnouncementSet), and then implementing #when:do: in the resulting ModelSet object. This made my code a little more compact:
(aStoryProject backlog , aStoryProject plan)
do: [:ann | ann treeModelUpdate: self model: treeModel];
when: StoryCardAddedAnnouncement, StoryCardRemovedAnnouncement
do: [:ann | ann recalculateFilterFor: self ].
I also created a hierarchy of StoryCard announcements, which I could then add methods to (in this case #treeModelUpdate:model: and #recalculateFilterFor:) and then call in my announcement Blocks. This change reduced a lot of clutter in my code, as these methods replaced the #onXXXX: methods that were sitting in several GUI components (some confusingly inherited, and some blatantly copy and pasted). The use of the "," operator for the Added and Removed announcements also feels a little more expressive as well.
As I worked through more examples, I also noticed that if my models start announcing changes, then I need a way to conveniently tie those announcements to GUI objects in a Dolphin manner:
| velocityProposer |
super model: anIteration.
teamSizePresenter model: (anIteration aspectValue: #teamSize).
velocityProposer := VelocityProposer for: anIteration.
(velocityProposer aspectValue: #suggestedVelocityRangeString).
suggestedVelocityPresenter model aspectAnnounces: VelocityChanged.
The above code shows how a normal number presenter can be bound to a #teamSize aspect of anIteration model. However it's also useful to bind to other types of aspects like #suggestedVelocityRangeString which can change dynamically by way of an Announcement (in this case VelocityChanged). This is analogous to the Dolphin #aspectTriggers: method, but instead works for announcements.
I'm still feeling my way through converting to using Announcements, however so far I have been quite pleased with the results. They haven't been ground breaking changes, but they certainly feel tidier
Announcements Framework for Dolphin Smalltalk
I while ago I read an interesting article about Cincom Announcements, which pointed out that while most things in Smalltalk are an object in their own right - triggered events have traditionally only been a Symbol (possibly due to the 640k limit of the early Digitalk Smalltalk).
For me, Smalltalk has always been a language and environment that has so many good examples of how things should be done (MVC, Refactoring, Images, IDE's etc), so understanding how this was fixed was something that greatly interested me.
I frequently program in Dolphin Smalltalk in my spare time, so I was curious how the use of Announcements might change my code. I had already noticed that as my Iterex tracking application became bigger it became more awkward to keep track of what events I was generating, and I was sometimes looking for a good place to put common functionality that appeared in event handlers (I have written about some of my experiences in a later post).
With this in mind, I decided to port the Cincom VisualWorks Announcements package to Dolphin to try it out. Unfortunately my initial attempt was not completely successful - the VisualWorks implementation is surprisingly large (although a large part of this is an extensive suite of unit tests). While I was able to quickly file-in the code and get most things to compile, there was a subset of tests that I couldn't get to pass. These dealt with a language feature of Cincom that is not available in other Smalltalk's - namely ephemerons. While I thought I understood the purpose of these, I was never able to code something that would emulate them to the extent that the tests would pass.
After a few attempts, it occurred to me that I had seen an implementation in Squeak Smalltalk and so I was curious about how this had been done. It turns out that there are actually 3 or more implementations, however I hadn't realised this at the time:
- A minimal implementation as part of the OmniBrowser framework and described in the thread: Announcements in Omnibrowser
- An extended version of the initial OmniBrowser implementation slightly generalized and separately packaged.
- A complete port of the original announcement framework as found in Cincom VisualWorks.
I started out with #2 above, and was quickly taken by how compact the implementation is, although I was also slightly disappointed with how small its suite of tests was (there are tests, but they are very minimal). To understand the limitations of the implementation, I decided to augment the basic tests with those provided with the Cincom implementation.
As I copied each test, I ran it, and if it failed I considered whether it was an important requirement for a basic implementation, or whether it was a more esoteric requirement that I could cover later (in that case I moved the test to a separate package). I ended up with an interesting hybrid of the two implementations.
The Squeak implementation #2 uses a global singleton announcer object whereas the VisualWorks implementation uses an event table on each object - I found that the latter is also much more natural in Dolphin, and in fact as there is already an event table for objects (either directly in the case of Model objects and their subclasses, or indirectly via an external dictionary for other objects) I was able to simply add Announcement in the same place.
I was also quite taken by both Squeak and Cincom's use of AnnouncementSet - and simply implementing concatenation on Announcement to return the composite Set:
^ AnnouncementSet with: self with: anAnnouncementClass
Furthermore, I was inspired by how the Squeak implementation takes this a step further and also delegates to Announcement or AnnouncementSet to see if it #handles: a particular announcement (this is a a neat simplification of code). This fitted in nicely with the Dolphin implementation as I simply added #handles: to symbol so that it can treat both Annoucements and Symbols similarly in the original code:
triggerAnnouncement: anAnnouncement for: anObject
self keysAndValuesDo: [:announcementKey :actions |
(announcementKey handles: announcementToTrigger)
ifTrue: [toAnnounce addAll: actions]].
announcementToTrigger class broadcast: announcementToTrigger to: toAnnounce
It's also worth mentioning that the last line of the above method paves the way for Meta-Announcments. By delegating to the Announcement class to broadcast its event you have control on how events are sent, and in the Dolphin implementation I have added the following:
broadcast: anAnnouncementInstance to: anEventSet
super broadcast: anAnnouncementInstance to: anEventSet.
self broadcastMeta: anAnnouncementInstance
Where #broadcastMeta: allows the client to subscribe to MetaAnnouncement's and hence subscribe to all announcements for debugging purposes or simply as a more convenient way to know that events have happened:
self announce: (MetaAnnouncement for: anAnnouncementInstance).
self == ObservableAnnouncement ifFalse:
[ self superclass broadcastMeta: anAnnouncementInstance].
I haven't played around with these MetaAnnouncments much (but did write some tests for them) - but certainly the debugging aspect strikes me as being particularly useful. This simple trick might be something interesting to roll into the Squeak and Cincom implementations.
Having successfully ported Announcements to Dolphin, I have found that they have simplified some of my code (a topic for a future post). I was also pleased to find that they work well with the Dolphin packaging system and I had no troubles creating my final .exe using the standard tools.
The final Dolphin packages can be downloaded from the Iterex Dolphin Announcements page.
Subscribe to Posts [Atom]