The Artima Developer Community
Sponsored Link

Agile Buzz Forum
A new twist on pluggable behavior

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
James Robertson

Posts: 29924
Nickname: jarober61
Registered: Jun, 2003

David Buck, Smalltalker at large
A new twist on pluggable behavior Posted: Jan 25, 2007 6:20 AM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: A new twist on pluggable behavior
Feed Title: Travis Griggs - Blog
Feed URL: http://www.cincomsmalltalk.com/rssBlog/travis-rss.xml
Feed Description: This TAG Line is Extra
Latest Agile Buzz Posts
Latest Agile Buzz Posts by James Robertson
Latest Posts From Travis Griggs - Blog

Advertisement

For a long time now, VisualWorks has had the AnnotatedMethod. For those that have never looked, this is a CompiledMethod subclass that has a slot for "meta data." One could simply make all CompiledMethods have a slot for meta data, but since few methods do have method data, there's a decent space optimization by having a separate subclass that only shows up when needed.

Originally, these were just for <primitive: 404> tags and the like. But then Vassili did some nice work in generalizing the syntax so that they are basically of keyword/literal argument form.

I tend to shy away from usage patterns that want to use them as "precompiler directiives." Such is not "the Smalltalk way" in my opinon. But they're very handy for reflecting about the behavior of an object. By tagging methods with these meta data chunks, we can "tag" a subset of an object's methods as being of interest for a particular task. And I've found more and more that this is handy way to have "pluggable" behaviors. The Settings tool is a good example of this. One can add a method to the settings tool that is to have no apparent UI effect. Or one can "extend" the UI, by simply "plugging in" a new method which is tagged as contributing to the elements that show up under a certain condition. The Settings framework is then able to search which of it's methods are applicable for given operations and just aggregate them.

For the recent changes I did to SUnitToo(ls), I wanted to experiment with this approach. It's a bit of overkill, but it was simple and it provided a good opportunity to try what I wanted. In that case, we had a method for determing what a class's testClass was. It involved probing Foo's environment for a class of name FooTest. A whole different approach is to check for the test package of Foo's containing package, to see if it has a class of name FooTest. Having both of these approaches in one place was big. So, I factored it:

guessTestClass
	^self guessTestClassByName ifNil: [self guessTestClassByPackage]
That's great. What if someone has a different method they want to use to infer what the associated TestCase is of a given class? They have to override the method. What if two people do so? Then we have problems.

So I used the tagging approach to do this. Basically, you add a method like:

guessTestClassTags
	<pragmas: #class>
	^#(#testClassQuery)
Then you can put <testClassQuery> at the top of any method you want to be part of this API. The guestTestClass method would be rewritten as something like:
quessTestClass
	(Pragma find: #testClassQuery in: self class) do: [:each | (self perform: each selector) ifNotNil: [:result | ^result]].
	^nil
Or something like that. I never actually did that, because that's not what I really wanted. See, that kind of thing is great for the Settings tool, but SUnitToo(ls) is doing that kind of query all the time as it constantly figures out how many tests it could run on any navigation. And it hits it lots.

So this is Smalltalk, right? All about "growing behavior", "runtime dynamism", "just in time gestalt." Why not do a little of both? Whenever AnnotatedMethods are added or removed to a class it will send classMethodsChanged (or instanceMethodsChanged if the tag was declared in the first using instance: instead of class:). So what it really does is this:

classMethodsChanged
	self regenerateGuessTestClassMethod.
	^super classMethodsChanged

regenerateGuessTestClassMethod
	| ws selectors methodHolder |
	ws := String new writeStream.
	ws
		nextPutAll:
			'guessTestClass
	^'.
	selectors := ((Pragma
		allNamed: #testClassQuery
		in: self class) collect: [:each | each selector]) asSortedCollection.
	selectors
		do:
			[:each | 
			ws
				nextPutAll: 'self ';
				nextPutAll: each]
		separatedBy: [ws nextPutAll: ' ifNil: ['].
	selectors size - 1 timesRepeat: [ws nextPut: $]].
	selectors isEmpty ifTrue: [ws nextPutAll: 'nil'].
	methodHolder := self compilerClass new
		compile: ws contents
		in: self
		notifying: nil
		ifFail: [^self].
	self class
		replaceMethodAt: methodHolder selector
		withMethod: methodHolder generate
So this is very cool (in my opinion). We dynamically adapt the main entry point method (getTestClass) to be an efficient composite of any query that someone wants to add. So we get the best performance possible. Some care is taken to make sure that we do this outside of the "change" system, so it doesn't go marking packages dirty and that kind of thing.

There's a parallel implementation for guessTestedClass down on TestCase. As I said, it was overkill for this case most likely. I really don't expect a high demand of people wanting to plug in additional algorithms for this. But it was a nice test run. I expect to use the technique elsewhere in the future in some areas that would really benefit from it.

As a final note, there is one whole in the system currently. And that is that everyone has to use the same methods (classMethodsChanged/instanceMethodsChanged) to respond to changes in the behavior of an object. So if we both want to add our own method tag types we can, but if we both want to respond to them, we've got collision again. There's a solution, but that's another day and another post.

Read: A new twist on pluggable behavior

Topic: Our Pals at the MPAA (et. al.) Previous Topic   Next Topic Topic: Something the Enterprisey won't grok

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use