Scalable switch statements

// Multiple "case" declarations are allowed; however "default" assigns only assigns its last declaration

let MySwitchObj = new switch( () => { "status code 04" } )  /* default */

MySwitchObj[12] = ()=> { "status code 00" }
MySwitchObj[13] = ()=> { "status code 01" }
MySwitchObj[14] = ()=> { "status code 02" }
MySwitchObj[15] = ()=> { "status code 03" }

let i = 0
MySwitchObj[0]      // "status code 04" //

MySwitchObj["default"] = ()=> { "status code 05" }
MySwitchObj["default"]      // "status code 05" //
MySwitchObj[0]      // "status code 04" //

MySwitchObj.default = ()=> { "status code 06" }
MySwitchObj["default"]      // "status code 06" //
MySwitchObj[0]      // "status code 04" //

MySwitchObj[12]     // "status code 00" //
MySwitchObj["12"]   // "status code 04" // 

MySwitchObj["12"] = (e) => { e }
MySwitchObj["12"]("status code 07") //  "status code 07" // 

Notationally, this is just wrong. None of those functions return anything but undefined. Let's ignore that for a moment. Second problem is that calling this a "switch statement" is again wrong. Sure, it can be used in a similar fashion given that each property is a "case", but there's little to no advantage in doing things this way over simply using a Map and proper call notation, or a constrained Proxy if you really want to skip the 2 extra characters per access.

There's also another big issue, you're trying to make the key "12" be different from 12. This is just not possible without altering the engine fairly severely. ES only supports Strings, Symbols, and (as of class fields) PrivateNames as keys. Suddenly changing things to allow the number 12 to be treated as a non-string numeric key would break a lot of programs.

This is what the thing you're trying to achieve looks like:

function SwitchObject(fn) {
   if (typeof(fn) != "function") throw new TypeError("Default function required.");
   let data = new Map();
   let dflt = fn;
   return new Proxy({}, {
      get(target, prop, receiver) {
         let retval = data.get(prop);
         return (typeof(retval) == "function" ? retval : dflt)();
      },
      set(target, prop, value, receiver) {
         if ((value != void 0) && (typeof(value) != "function")) {
            throw new TypeError("Value must be undefined or a function.");
         }
         data.set(prop, value);
         return true;
      }
   });
}

let MySwitchObj = new SwitchObject( () => "status code 04" )  /* default */

MySwitchObj[12] = ()=> "status code 00"
MySwitchObj[13] = ()=> "status code 01"
MySwitchObj[14] = ()=> "status code 02"
MySwitchObj[15] = ()=> "status code 03"

let i = 0
MySwitchObj[0]      // "status code 04" //

MySwitchObj["default"] = ()=> "status code 05"
MySwitchObj["default"]      // "status code 05" //
MySwitchObj[0]      // "status code 04" //

MySwitchObj.default = ()=> "status code 06"
MySwitchObj["default"]      // "status code 06" //
MySwitchObj[0]      // "status code 04" //

MySwitchObj[12]     // "status code 00" //
MySwitchObj["12"]   // "status code 04" // 

If you'll note, I omitted the last entry where you're replacing 12. This is because it just doesn't work. You've only got 2 choices: return the function or call it. That's not to say it cannot be made to work. With a simple check of value.length, I can make it so that if it's > 0, then it returns the function. However, that's just plain inconsistent behavior.

This feels very similar to a default-dict that many other languages implement. If one were implemented in Javascript, we could do something like this:

const myDict = new DefaultDict(() => () => "status code 04")

MySwitchObj.set(12, () => "status code 00")
MySwitchObj.set(13, () => "status code 01")
MySwitchObj.set(14, () => "status code 02")
MySwitchObj.set(15, () => "status code 03")

MySwitchObj.get(12)() // status code 00
MySwitchObj.get('unknown prop')() // status code 04
1 Like