Eliot Miranda writes a great and lengthy dissertation about the value of first class contexts. This is a recurring theme in Smalltalk. It's that everything is objects, all the way down, at least to a depth not found in peer OO languages. It's not just the contexts either.
This post shows how we do instance mutants in Smalltalk, and yet another example of first class everything.
I had a hard test to write the other day. It involved two processes and potential race condition involving interruptWith: and terminate, both Process APIs. It was meant to test some code that looked, simplified, like:
watchedProcess := Processor activeProcess.
alarm := [aDuration wait. watchedProcess interruptWith: ["something happens"]] forkAt: watchedProcess priority + 1.
["do some stuff] ensure: [alarm terminate]
The problem was that I needed/wanted to emulate reliably the rare case where
watchedProcess was finished, but there was enough life left in
alarm to still do the interrupt block. What I needed, for the duration of just this test, was for
alarm terminate to not happen.
Smalltalk behaviors are just first class method dictionaries. Every object's class is one. These are usually ClassDescription objects, found through the normal directed graph of classes you see in your browser. But they don't need to be. Consider the following example:
| p |
p := 4 @ 3.
p negated
This produces the result of (-4 @ -3). But what if I wanted for some absurd reason to change Point behavior, not for the whole system, because likely things would come crashing down pretty fast, but just for that instance, for a bit. We could modify the code to read:
p := 4 @ 3.
p changeClassTo: (p class copy superclass: p class).
p class methodDictionary at: #negated put: (p class methodDictionary at: #transpose).
p negated
Now it returns (3@4) instead of the negated value. Just that instance. No other Point in the system will behave that way. Just the mutant one I made. What's going on? The first new line added, is how create a mutant instance. A copy is created of it's defining class, that copy is made a lightweight subclass of the original Point class we know about. We say it's lightweight because, Point doesn't know it as a subclass, so it won't show up in the system tools. And we change the instance class to be this no anonymous copy.
The third line, is then free to modify the new anonymous behavior, which it does by mucking with the methodDictionary directly, replacing the #negated method with the #transpose one.
Other than raising the hackles of the security minded, what, if anything is this good for? It can be handy for instrumenting things to discover behavior. It can be handy for temporarily "fudging" or "faking" a computation, for testing purposes, or other other code discovery. In the case of my test, it was handy for causing my alarm process behave a little differently than processes normally do, thus allowing me to reliably create what was otherwise a rare luck of the roll to get the timing right.
I was able to modify my test to read something like:
thatProcess := self codeToFindThatOneInstance.
thatProcess changeClassTo: (thatProcess class copy superclass: thatProcess class).
thatProcess class compile: 'terminate "do nothing"'.
....
Using this technique, I was able to force a process under test to take up some mutant behavior to cause the tested scenario to recur reliably. Because not only are contexts real objects, and classes first class objects, but so too are the very Processes themselves.