Hacker News new | past | comments | ask | show | jobs | submit login
Using Dispatch Tables to Avoid Conditionals in JavaScript (designpepper.com)
46 points by shawndumas on May 13, 2013 | hide | past | favorite | 46 comments



You can further improve dispatch tables in js by using bind.

so something like:

  north: function() { movePlayer("north"); },
becomes:

  north: movePlayer.bind(this, "north"),
or using underscore.js(for greater browser compatibility):

    north: _.bind(movePlayer, this, "north"),


or instead of a whole library use mozilla's compatibility when using bind... see the function here: https://developer.mozilla.org/en-US/docs/JavaScript/Referenc...


Because modifying object prototypes is the way to go?


Depends if you're in an environment you control then yes - when embedding your scripts, don't do it. Create your own isolated functions and assume anything and everything can be broken e.g. Array.prototype is often modified with broken or semi incomplete functions like broken versions of indexOf


If you use es5-shim (https://github.com/kriskowal/es5-shim), you can say movePlayer.bind(this, "north") on older browsers.


How is this an improvement?

It looks basically equivalent but more confusing to me.


It's just a way of getting rid of the redundant "function()" syntax. Unfortunately with bind(), we have to set the scope even though it isn't always necessary. So in this case, the first parameter "this" is also probably redundant. The old PrototypeJS curry() would have been the least redundant solution: movePlayer.curry("north")



It would be nice if they had called it curry() or at least created a curry() alias.


Nice, hadn't seen that before, looks like it was just added early this year.


While this can be a useful construct when you are doing meta-programming, this particular example is horrible. It's not much more readable (would be less readable in an 80x24 terminal window), and now you have an extra object sitting in memory for the lifetime of your application. If you only have a handful of options, just use switch.


>and now you have an extra object sitting in memory for the lifetime of your application

Worrying about that would be the epitome of premature optimization.


Also, there is still a switch needed in the MovePlayer function.

So all that extra code only serves to eliminate 2 options from the original switch.

I do like the approach in some cases, I just think that the example given probably wasn't the best choice.


The example definitely leaves a bit to be desired.

One great story for why this way might be better is that with a small tweak (to allow dynamic [de]registration of command functions) it is much more open to extension.

To continue using the somewhat creaky "text game" example, if possessing certain items or being in a particular room affects the available command set, this can be easily included: without a magic feather, "fly" returns "You don't know how to do that" but when you pick up said feather a new action is registered, and now "fly" works properly.

Now, I'm not sure I'd implement this particular game that way, but such an interface tends to be useful for building plugin systems, etc.

It's mainly for this reason that large switch statements, particularly those not doing "math" of some sort, tend to be a code smell.[1]

[1] http://en.wikipedia.org/wiki/Open/closed_principle


I use something similar when rendering form fields dynamically.

    draw_checkbox = function() {}
    draw_radio    = function() {}
    draw_select   = function() {}

    draw_field = function(type) {
      this['draw_' + type]();
    }

    draw_list = function(list) {
       for(i = 0; i < list.length; i++)
         draw_field(list[i].type);
    }
For sake of simplicity, I did not include the code to verify that type is proper and parameters sent to each function.


This is probably the part I dislike most about ruby (I know this is JS). There are some places where this trick makes a painful piece of code go away, so I appreciate why it was selected. The problem is that you now have no reference to draw_checkbox being used in your entire code base and when someone else is refactoring, they think they can remove it.

Personally, I would never elect to go this route because it only introduces overhead for refactoring and code maintenance. I would rather see a mess of procedural code, than some silly clever little tricks that will trip up all the new people.


yeah, i kinda agree.. in this case I'd have done something more like:

   function Renderer() {}
   Renderer.prototype = {
     draw: {
        checkbox: function() {},
        radio: function() {},
        select: function() {},
        field: function(type) {
          this[type]();
        },
        list: function(items) {
          return items.map(function(item) {
            return this.field(item.type)
        });
       }
    }
   }
Perhaps I'm just comment trolling though - totally depends on how the code would have been used...


Actually my code ends up looking pretty much like yours. I use CoffeeScript and was writing a simplified JS equivalent off the top of my head since this post was about JS. Similar to your Renderer, I have a DrawForm class that iterates through DrawField class objects which provide the checkbox(), radio() etc. methods.

My point was that in the end, you get to do dynamic function instantiation, which is pretty awesome in JS.


I'd prefer to see the functions put in a dictionary and invoked from there rather than named individually in the local scope, easier to see at least what you have to look for when you want to see if they're referenced, and you'd have dict[item] rather than "dict" + item.

Edit: taf2's seems more idiomatic, js is not my first language.


I've done this before in scenarios of packet handling. e.g.

    packets.incoming[16] = {
        login_size:     {type: 'byte'},
        magic_code:     {type: 'byte'},
        client_version: {type: 'short'},
        high_detail:    {type: 'byte'},
        archive_crc:    {type: ['int', 9]},
        reported_size:  {type: 'byte'},
        block_opcode:   {type: 'byte'},
        client_key:     {type: ['int', 2]},
        server_key:     {type: ['int', 2]},
        uid:            {type: 'int'},
        username:       {type: 'string'},
        password:       {type: 'string'}
    };

    packets.outgoing[253] = packets.outgoingMessage  = function (message) {
        var length = message.length + 1;
            return {
                opcode:    {type:'byte',          value: 253},
		length:     {type:'byte',           value: length},
		message:	{type:'string',        value: message}
            };
    };


"Dispatch table"? I'm surprised everything in programming has a name associated with it. I just called these Javascript objects representing a map of functions...


lol yeah, in some languages they even call that a class!


30 years ago we called this a vector table on a microprocessor. What's old is new again...


I don't know about you, but I'm digging these thinclient+mainframe [cloud-based] apps.


Agree. But I think there's a great future in PC (Personal Cloud) systems. Lots of potential there.


Interesting. Ive seen something similar to this quite often used in Python because it doesn't have Switch statements.


Indeed, it's quite idiomatic. I would have still preferred if there was a switch/case construct that would generate a dispatch table for us (in fact, this was proposed and rejected in pep 3103[1]). A case statement would have been a nice, little addition to the expressiveness of Python, but it's not that big of a deal.

Here's another dispatch table technique that I really like[2].

[1]: www.python.org/dev/peps/pep-3103/

[2]: http://code.activestate.com/recipes/577864-fast-and-elegant-...


Is it just me, or for this example does the "dispatch table" seem way more complex (and likely less efficient) than the switch statement? The original is not difficult at all to interpret.


The dispatch table is shorter than the switch, so there is a lot less noise. The invocation is a little clever, but overall I would consider them about equally complex. Maybe you meant unconventional?

The performance is substantially different. The dispatch table is ~75% slower (in chrome). http://jsperf.com/switchvsdispatchtable

One thing I really like about dispatch tables is that it forces the developer to only be able to handle the decision logic. The number of times I've had to cleanup a switch with 20-30 lines under each condition (sometimes including another switch) is too many to count.


> The performance is substantially different. The dispatch table is ~75% slower (in chrome).

Of course those are, they're doing very different things in that test case. Your first case is just returning a value, and the second is executing a function to return a value. Here's a better comparison, http://jsperf.com/switchvsdispatchtable/2

There's nothing inherently slow about dispatch tables, it all about how you implement them. As someone else mentioned, the OP should be using bind to reduce the # of function calls instead of executing a function which executes a function.


Yes it is just you. :) The original becomes horrible when the number of cases grows, when you want a default and can introduce subtle bugs if you forget a break. In the second example, there is a clean separation between processing commands and dispatching on the input. For example, you might want to extend processUserInput with "invalid command" handling, commands with arguments or even add commandTable as an argument to it so that you can reuse it for all your dispatching needs.

And to top it of, you can dynamically add new commands to the dispatch table which you can't to a switch.


I'd like to see you hunt for the dynamically added case to the dispatch table. Maybe then, you'll change your mind.

There is no fundamental difference between either approaches except the runtime cost. If you need conditonals, use them. If you need a lookup table, use them. Keep the code predictable.


It depends, sometimes if you have a few different context e.g. multiple click handlers that are being bound in different contexts of your app, that a table makes a lot more sense then repeating the switch or even wrapping the switch in a function...


I think the real win with a technique like this is that, if you store the bindings in a place that is globally accessible, it is open to extension.

For example, if a JSON library author implemented an encode(object) function using a dispatch table holding a set of object-type/JSON-encoding-function pairs, a client could extend the encode function by registering additional object-type/JSON-encoding-function pairs in the dispatch table.

Edit:

Furthermore, I believe this is how clojure protocols work - when a new type participates in a protocol, the type-specific function implementation gets added to a dispatch table keyed on the datatype's java Class. Then, anytime you invoke the function, the class of the first argument is identified and the corresponding type-specific function is pulled out of the dispatch table based on the class.


You have to be careful when doing this type of stuff. For instance, the switch case is actually doing both validation and switching, where as the dispatch table does no validation.

Consider: processUserInput('toString');

That has very different behavior in the two different styles.


I don't know, in this case, with a bit of reformatting the switch can look pretty similar:

    function processUserInput(command) {
        switch (command) {
            case "north":    movePlayer("north"); break;
            case "east":     movePlayer("east");  break;
            case "south":    movePlayer("south"); break;
            case "west":     movePlayer("west");  break;
            case "look":     describeLocation();  break;
            case "backpack": showBackpack();      break;
        }
    }
Although that's to be expected, a switch on a string value isn't that much different to a string lookup in an associative array.


Except with an array you can change things programmatically on the fly. Or have it setup in a configuration file or database table. Very useful.


The switch statement offers the ability to provide a default opperation, such as tell the user the command was not found.

You could add an undefined property check on the dispatch table, but then you have as much and more complex code IMO.


I think the article does a poor job of describing why the dispatch table version is better, or why you'd want to avoid conditions in the first place.


I've done something similar in my form-validation library. I have a function that has three inputs that basically end up behaving like flags. So there are 8 possibilities in total. I ended up creating a function that would return a three-bit binary number based on the inputs, and I also created a jump-table that maps the three-bit binary values to specific handlers.


Hey, I used a modified version of this pattern without knowing what it was called a few months ago: https://github.com/thomasboyt/Noted-App/blob/master/app/asse...

Really useful when you have lots of keybindings.


This example is great to point out that javascript is a dynamic language and should be used as such whenever possible. obviously he only used a handful of options and maybe a switch would work better for this exact example, but it should get the reader thinking about where else this type of pattern could be used.


For more complicated scenarios (where you find yourself making trees of conditionals), I suggest reading up on finite state machines, and for js checkout machina:

https://github.com/ifandelse/machina.js


This is an excellent tip and makes one wonder why it isn't more commonly seen in the wild.


I'm guessing its just not a common thing to have a large definition of single line if statements, I'm not sure about other programmers but I don't think I could find much of this in my code.


I don't see the benefit. it really is just a disguised switch statement, unless you do something more dynamic with the mapping between the command name and the function to call...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: