Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

A switch statement allows you to have different behavior for different inputs, but often all that's needed is different output data.

If you have to map, say, country codes to country names, writing a long switch statement of case "US": name = "United States of America" break

Is going to suck. An object (in js, an associative array more generally) will be simpler.

It's not always quite so obvious as that example.



Thanks for the example! I guess we consider switch/case in different situations then. I usually make use of switches in simple mappings and switch/case seems more readable and idiomatic to me there:

  type CountryCode =
    | "CH"
    | "US";

  function nameFromCountryCode(countryCode: CountryCode): string {
    switch (countryCode) {
      case "CH": return "Switzerland";
      case "US": return "United States of America";
      default: // exhaustiveness checking
        ((val: never): never => {
          throw new Error(`Cannot resolve name for countryCode: "${val}"`);
        })(countryCode);
    }
  }
...instead of...

  type CountryCode =
    | "CH"
    | "US";

  function nameFromCountryCode(countryCode: CountryCode): string {
    return ({
      'CH': (() => 'Switzerland'),
      'US': (() => 'United States of America'),
    }[countryCode] || (() => {
      throw new Error(`Cannot resolve name for countryCode: "${countryCode}"`);
    }))();
  }


Your second example is not how anyone here is recommending using an object to replace a switch. You've still got the function call, which is now redundant.

Here's how you'd actually do it:

    type CountryCode =
      | "CH"
      | "US";


    const countryNames: Record<CountryCode, string> = {
      "CH": "Switzerland",
      "US": "United States of America",
    }
Your second example is way more complicated than your first, but this one is even easier to read (at least for me), and still provides all the same functionality and type safety (including exhaustiveness checking).


I'll bite: why the anonymous function throwing an Error in the first snippet?


I didn't lay out a bait =)

It would also look more readable to me with a default return value. An exhaustiveness check just keeps your mapping functionally pure and the type checker can catch it.


@lolinder

...you just removed the default case and just introduced undefined as return value at runtime, so it isn't the same functionality.


First of all, can you just reply? It does weird things to the threading when you don't.

Second, removing the default case is part of my point.

You were writing TypeScript code, not raw JS, and in my improved example the type checker won't let you try to access countryNames["CA"] until "CA" has been added to CountryCode. Once "CA" is added to CountryCode, the type checker won't let you proceed until you've added "CA" to the countryNames. The only situation in which a default case is needed is if you throw an unchecked type assertion into the mix or if you allow implicit any.

With implicit any turned off, this code:

    const test = countryNames["CA"]
Gives this error:

    TS7053: Element implicitly has an 'any' type because expression of type '"CA"' can't be used to index type 'Record<CountryCode, string>'.   Property 'CA' does not exist on type 'Record<CountryCode, string>'.


the reply button wasn't there and I assumed the reach of a depth limit... guess I just needed to wait a bit ¯\_(ツ)_/¯

Moving the case for a default value to the caller is a weird choice IMHO. Types should reflect the assumed state about a program, but we all know the runtime can behave in unexpected ways. Assume an SPA and the response of an API call was typed with CountryCode in some field, then somebody just worked on the API - I prefer to crash my world close to were my assumption doesn't fit anymore, but YMMW.

Your implementation (and safety from the type checker) only helps at build time and puts more responsibility and care on the caller. That implementation could prolong the error throwing until undefined reaches the database, mine could already crash at the client. Either TS or JS will do that.


> Assume an SPA and the response of an API call was typed with CountryCode in some field, then somebody just worked on the API - I prefer to crash my world close to were my assumption doesn't fit anymore, but YMMW.

Agreed on crashing, but I prefer to push validation to the boundaries of my process and let the type checker prove that all code within my process is well-typed.

Defensive programming deep in my code for errors that the type checker is designed to prevent feels wasteful to me, both of CPU cycles and programmer thought cycles. Type errors within a process can only occur if you abuse TypeScript escape hatches or blindly trust external data. So don't cast unless you've checked first, and check your type assumptions about data from untrusted APIs before you assign it to a variable of a given type.




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

Search: