The crux of his method was the building of an anonymous class that
would respond correctly to his callbacks
For some reason this post captured my imagination and I started to
deconstruct his method, as I couldn’t quite grasp why it worked. What
follows is a deconstruction of Matt’s code.
His example involved a small handler for “posting” twitter messages:
I’m going to work backwards, substituting and expanding this code
until we reach a good position to see what’s happening. We’ll ignore
Matt’s “bonus” style for focus. Firstly, just expand the call to
#tweet, and define the block as a local lambda:
let’s ignore the rescue clause. In fact we can trim everything
except for one of the block.callback calls. So, going with
:success:
At this point we need to go back to our original definition of
Proc#callback
Expanding out Proc#callback, and realising that in this context,
self is block, callable is :success, and args is []. In
the case of the :failure callback, args would be the parameters
passed to the callback.
Simplifying the definition of block as a lambda and the Proc#===
call (Another neat thing that I learnt from Matt’s post), our code
becomes
Time to simplify the anonymous class definition, let’s get rid of the
error handling in #success too.
Hey, now we are getting somewhere! Let’s give the anonymous class a name.
So what has happened? We’ve created a SuccessCallback class that
only responds to #success. And all #success does is call a block
passed to it. Any other method call will absorb any parameters and
any block passed. This is the heart of Matt’s system, as we can see
in the next expansion, where we unwrap the callback lambda.
And it becomes clear from looking at the definition of
SuccessCallback why the callback works. on.success trampolines (to
play fast and loose with the term) to the block passed, and
on.failure falls through to SuccessCallback#method_missing and
thus returns false, ignoring the passed block.
In the :failure case, we would have ended up with:
So, in essence, Matt’s method works by:
Defining an anonymous class that responds to a single method,
#success in our example, that calls straight back to a block passed.
Yielding an instance of this anonymous class to the callback block.
The callback block calling every event method on the anonymous
instance. However, since the anonymous class ignores all messages bar
the event that was “triggered”, the callback only executes the block
matching the trigger.
Writing out these steps, I feel a bit sheepish in taking so long to
grasp how exactly Matt’s code worked. But what we can do is extract
out the concept embodied by the anonymous class, and reformulate the
code.
As a pedagogical exercise, this formulation allows me to more clearly
understand the roles of each actor in this code, although Matt’s
method is more concise. I’m curious whether a class such as
SavantTrampoline (to coin a poor name), would be useful in other
contexts, maybe I should ask Reg
Braithwaite, as his writings on
combinators
has been a big inspiration for me to do this kind of investigation.