As a curiosity, I wondered what a simple piece of Smalltalk might look like if expanded out in to Continuation-Passing-Style. The real question of whether CPS is viable in Smalltalk is this: How many method permutations are required
What do I mean by that? Take the method isNil. In smalltalk, this method takes no arguments, down at the VM level it takes one argument - the receiver. In CPS there is no 'return', so any information you wish to use ahead of you must be passed as an argument:
a := 6. myBool isNil ifTrue: [self doSomething]. a doSomethingElse
In this scenario, the variable a needs to be passed through all the calls that can get to a doSomethingElse. So we'd end up with a new variant of isNil that looked something like:
Object>>isNil: continuationReturnLocation theVariableA: a continuationReturnLocation(false,a)
Smalltalk already has a Message object which can be used to tell some code where to go and what to pass it, so instead we can do the following:
Object>>isNil: continuation continuation value: false
And to call it:
myBool isNil: (Message receiver: self selector: #nextBit:continuation: arguments: (Array with: #returnValueGoesHere with: continuation)
As noisy as this is, it shows us that we don't need a million and one variations of every method in the system to implement CPS - we just need to make lots and lots and lots of Continuation/Message objects.
So! What would the world look like if we had a VM create lots and lots of Continuation objects instead of making lots and lots of method call variants? Well, lets take the following piece of code:
asRpcXml | aStream numberOfArguments | numberOfArguments := arguments isNil ifTrue: [1] ifFalse: [arguments size]. aStream := WriteStream on: (String new: numberOfArguments * 100 + 100). self printRpcXmlOn: aStream. ^aStream contents
Then we bloat it out using Continuation/Message objects
asRpcXml: continuation arguments isNilContinuation: (Message receiver: self selector: #asRpcXmlBranch:with: arguments: ? and: continuation) asRpcXmlBranch: isNil continuation: continuation isNil ifTrue: (Message receiver: self selector: #asRpcXmlBranchTrue: argument: continuation) ifFalse: (Message receiver: self selector: #asRpcXmlBrancFalse: argument: continuation) continuation: (Message receiver: self selector: #nowhere) asRpcXmlBranchTrue: continuation 1 times: 100 continuation: (Message receiver: self selector: #asRpcXmlBranchTruePlus:continuation: arguments: ? and: continuation) asRpcXmlBranchFalse: continuation arguments sizeContinuation: (Message receiver: self selector: #asRpcXmlNewString:with: arguments: ? and: continuation) asRpcXmlBranchTruePlus: number continuation: continuation number plus: 100 continuation: (Message receiver: self selector: #asRpcXmlNewString:continuation: arguments: ? and: continuation) asRpcXmlNewString: size continuation: continuation String new: size continuation: (Message receiver: self selector: #asRpcXmlWriteStreamOn:continuation: arguments: ? and: continuation) asRpcXmlWriteStreamOn: string continuation: continuation WriteStream on: string continuation: (Message receiver: self selector: #asRpcXmlPrintOn:continuation: arguments: ? and: continuation) asRpcXmlPrintOn: stream continuation: continuation self printRpcXmlOn: stream with: (Message receiver: self selector: #asRpcXmlReturn:continuation: arguments: stream and: continuation) asRpcXmlReturn: stream continuation: continuation stream contentsContinuation: continuation
This a thought experiment. What I'd really like to do is to find some easy way of measuring how many method permutations are required to implement the VisualWorks code base using non-context object based CPS. Thoughts?