Metaprogramming is code that operates at the highest levels of abstraction so as to reduce total lines of hard coded processes and data. In addition to concision, it also allows flexibility for future edits.
One of the various metaprogramming concepts that we’ve covered in lecture at the Flatiron School includes dynamic dispatch. That is, calling methods on the fly, without knowing whether the given method exists for the object upon which it acts.
In this post, I’ll take a valiant (and hopefully unfailed) stab at the concept and its uses.
Now, the way to dynamically dispatch in Ruby is by using the
.send method. Let’s demonstrate with a very basic example:
.send acts upon “Hello World”, with “upcase” passed in as an argument, either in string or symbol form. In my basic interpretation,
.send essentially relays a message (i.e. the argument) to the object, converts the argument into a method, and subsequently determines whether or not it exists for the given object. If so, Ruby evaluates it:
.upcase. As it’s a valid method for strings, Ruby evaluates it accordingly. But let’s see what happens when we attempt to pass in this argument while evaluating an integer:
We see this result:
Ruby returns an undefined method error message, as it recognizes that no such method exists for integers.
While the above are examples of methods that take no arguments,
.send also accommodates ones that require them. Any remaining items within the parameters are passed into the method that
.send in this instance, with
self referring to the instance of the main class, returns:
By now, I’ve laid out the basic groundwork for dynamic dispatch. As I’ve so far explained it disembodied from any context, you may wonder why we don’t directly call the method itself, rather than circuitously passing it through
Dynamic dispatch, as with all techniques of metaprogramming, can help write cleaner code. Let’s say we have a
class HelloMachine, which allows you to decide which language to greet in.
Notice how repetitive and cumbersome the
if statement contained within the
speak method is. We can easily pare this down using dynamic dispatch:
Here, we have created an
APPROVED_COMMANDS array, containing symbols of potential commands to enter into the
if statement tests to see if the user’s input is contained within this array.
If so, the
.send kicks in, acting as a pivot that hands off the user’s command to one of the four existing methods: french, english, chinese, spanish.
.send in conjunction with an array of potential methods, we’ve created code that is not only more succinct and encapsulated, but also more flexible. Any future methods would only require that you enter the corresponding symbol into the
APPROVED_COMMANDS array, as opposed to hard coding even more “if” statements.
It’s also important to note in the example above how
.send, while helpful in code concision, can expose certain security holes. The
APPROVED_COMMANDS array is crucial in this scenario, as it limits the scope of what users can direct over.
Think, for example, what would happen if one were to
"system", "rm -rf /". You’d be …rF’ed!
Clean code is nice, but the true beauty of dynamic dispatch comes in when we do not yet know the name of the method in advance—when it’s still represented in either string or symbol form. As this opens up another can of worms, I ask that you stay tuned: in a future post, I’ll follow up on dynamic dispatch and attempt to tackle how it might tie in with dynamic definition, in which we program to not only call, but create methods on the cuff.