DEV Community

philip-haines
philip-haines

Posted on

Dynamic Dispatch and Dispatch Tables

Starting with the Computer Science Theory

The first time I heard about dynamic dispatch I was shocked, confused, intrigued, and excited all at the same time. It was an enlightening moment. I barely grasped the concept at the time, but through some practice and research came to the understanding that dynamic dispatch, and dispatch tables are truly the way.

When we talk about dynamic dispatch there are really two types of languages, there are static languages (C++, Java), and there are dynamic languages (JavaScript, Ruby, Python). The difference between a static language and a dynamic language is what happens at runtime.

At its core dynamic dispatch is when a program determines what chunk of code is going to be run when you send it a message.

A great example of this is console.log(). Have you ever actually looked at console.log() and broken down whats happening? It's fascinating. Console is a global object in JavaScript, and log is method on object. The "log" part of console.log() is the message. When you use a console.log() you are sending the message “log” to the console object with some data attached to it, console then looks up the method "log" and runs it showing you the argument that you passed in when calling console.log().

The way console.log() works could not work in a static language as at runtime a static language needs to know exactly what is going to happen in every function, and where all of that code lives. The fact that the message of log is being passed into the console object during runtime is what makes JavaScript a dynamic language.

So, let's look at how this works in practice. In Ruby, inheritance and prototype chains are common occurrences. A prototype chain is a chain of inheritance where attributes are passed from once object model down to the other.

Let's say we have a model for a Dog. The Dog class inherits from a Mammal class, the Mammal class inherits from the Animals class, and the Animal class inherits from the Object. The prototype chain would look something like this:

Dog < Mammal < Animal < Object
Enter fullscreen mode Exit fullscreen mode

Our Dog model has a method to make the Dog bark, and that method can be called with Dob.bark. Since Dog inherits from Mammal and so forth, Dog also has access to the methods belonging to Mammal, Animal, and Object. If we want out Dog to breath(which I think we do!) we can all Dog.breath. However, the breath method doesn't belong to Dog, it belongs to Animal, and this is where the beauty of dynamic dispatch kicks in.

At runtime our program evaluates Dog.breath and looks for a method of "breath" in the Dog class, when it cannot find it there, it looks for the method in Mammal, when it cannot find it there the program continues up the prototype chain until it finds the method and runs the associated task. This would simply not work in a static language, and would cause some potential errors.

Ok, ok, enough with the theory side of things, let's look at some of the cool stuff we can do with our new knowledge.

Let's pretend that we are making a game, and in our game our user can hit arrow keys, and the character turn the direction of the arrow keys. In order to know what to do when a key is pressed, there needs to be some logic programmed into the computer. To start let's make some methods that will handle a key response. For the purposes of this article, these methods will print the string for the corresponding key.

Screen Shot 2021-04-15 at 9.48.55 AM

So now that we have these functions, lets implement some conditional logic to call the corresponding function with a simple if/else statement.

Screen Shot 2021-04-15 at 9.52.32 AM

Now, theres nothing wrong with handling the logic this way. It satisfies rule number one "Make it Work", but it's clunky. If we had more conditionals who knows how long this if/else statement could get, and it's not very dynamic. If we want to add a new conditional we need to find where the statement is stored and then write in a new else if line. There are better ways. One would be to use a switch statement.

Screen Shot 2021-04-15 at 9.54.54 AM

The switch statement was made for this! It shines here, just look at all of the beautiful logic thats happening. This works, and it works well, but it could be better. with all of the break keywords in there case statements are difficult to reach through, and we still haven't solved how to add a new condition on the fly... Enter the hero of our story, the dispatch table.

Screen Shot 2021-04-15 at 9.57.04 AM

A dispatch table is just an object with key-value pairs. The keys are the cases from the switch statement, and the values are the functions which print our string directions. When setting up a dispatch table this way, it's important to note that the functions are not being invoked in the dispatch table and are just giving the instruction of the function to the object. By doing this, it makes importing the dispatch table much easier, as you know exactly where all of your functions are running.

So, how do we trigger our functions? In a real fame you would have a loop that listens for a key press, and inside of that loop there would be a function to trigger the dispatch table like so:

Screen Shot 2021-04-15 at 10.04.00 AM

All that is happening here is that at the key of the user input, just like any other object, the dispatch table will look at the value, evaluate it, and then we invoke the function that it finds based on the key. It really is that simple!

One last example to truly show off how dynamic a dispatch table can be would be a calculator. In this fake calculator there are going to be methods for math operations and will be set up in a dynamic dispatch table like so:

function add(x, y) {return x + y;}
function subtract(x, y) {return x - y;}
function multiply(x, y) {return x * y;}
function divide(x, y) {return x / y;}

let dispatch = {
    "+": add,
    "-": subtract,
    "*": multiply,
    "/": divide,
}; 

let x = 5;
let y = 4;
let operator = "+";

dispatch[operator](x, y);
Enter fullscreen mode Exit fullscreen mode

Once the data is collected from user input, all that needs to be done is to look up the key of operator in the dispatch table, and run the correct function. If a programmer wanted to come in later and expand the functionality of the calculator app they could do so wherever they like by simply setting a new key value pair in the dispatch object. An example would be a function that allows users to use exponents.

function exponent(x, y) {
    return Math.pow(x, y);
}

dispatch["^"] = exponent;
Enter fullscreen mode Exit fullscreen mode

In a static language, that would not be possible, because at runtime, the dispatch table is locked with the key value pairs that were programmed into it, and new key value pairs cannot be added on the fly.

Top comments (0)