DEV Community

Discussion on: βœ”||🀒 Commit or Vomit | Switch(true)

Collapse
 
peerreynders profile image
peerreynders • Edited

What do you think about switch(true) in general?

πŸ‘Ž

With that out of the way I do think it's worth hypothesising how code like this may have come in to being.

But even before that I'm surprised nobody claimed primitive obsession given that there are five distinct boolean values on the "loose".

Seems the more likely scenario should be something like:

const results = {
  noShow: false,
  immediateAvailability: true,
  experience: {
    angular: false,
    react: false,
    vue: true,
  },
};
Enter fullscreen mode Exit fullscreen mode

With that reorganization my next guess should make more sense. I suspect the switch(true) is a degenerate form of some copy-pasted code that was trying to approximate pattern matching.

While a number of languages support pattern matching, JavaScript only supports destructuring assignment β€” not pattern matching:

The big difference is simple: Pattern matching is a conditional construct and destructuring isn’t.

There is a TC 39 proposal to add pattern matching to JavaScript (which hasn't moved beyond stage 1 since 2018). Meanwhile there are some third party libraries available that approximate some pattern matching functionality.

Using Pattern Matching:

import { when, _ } from 'pattern-matching-js';
// https://github.com/klo112358/pattern-matching-js

const STATUS = {
  reject: 'nope',
  accept: 'hire',
  reevaluate: 'maybe'
};

const results = {
  noShow: true,
  immediateAvailability: true,
  experience: {
    angular: false,
    react: true,
    vue: false
  }
};

/* eslint-disable no-unexpected-multiline */
// prettier-ignore
const status = when(results)
  ({ noShow: true }, STATUS.reject)
  ({ experience: { angular: true } }, STATUS.accept)
  ({ experience: { react: true } }, STATUS.accept)
  ({ experience: { vue: true },
     immediateAvailability: true }, STATUS.accept)
  (_, STATUS.reevaluate)
  ();

console.log(status);
Enter fullscreen mode Exit fullscreen mode

The actual proposal has much richer functionality that would allow for the aggregation of all the "accept" patterns. But if you squint, that when (or match) is reminiscent of a switch.

There is another way to represent boolean values - as bit flags.
Using bit flags/field operators:

const STATUS = {
  reject: 'nope',
  accept: 'hire',
  reevaluate: 'maybe',
};

const FLAGS = {
  noShow: 0x01,
  availableImmediately: 0x02,
  angular: 0x04,
  react: 0x08,
  vue: 0x10,
};

const results = FLAGS.availableImmediately | FLAGS.vue;
const status = interviewStatus(results);

console.log(status);

function interviewStatus(results) {
  if (match(results, FLAGS.noShow)) return STATUS.reject;

  if (
    match(results, FLAGS.angular) ||
    match(results, FLAGS.react) ||
    match(results, FLAGS.vue | FLAGS.availableImmediately)
  )
    return STATUS.accept;

  return STATUS.reevaluate;
}

function match(value, flags) {
  return (value & flags) === flags;
}
Enter fullscreen mode Exit fullscreen mode

Again interviewStatus() is somewhat reminiscent of a switch statement wrangled to behave like an expression.

One final point: readability.

It's kind of interesting how many comments claim that if based solutions are more readable. Personally I find that most of the if based proposals require a prolonged mental parse to just get the gist of the code. Obviously readability is a lot more subjective than most people are willing to admit.

Using the nullish coalscing operator:

const STATUS = {
  reject: 'nope',
  accept: 'hire',
  reevaluate: 'maybe',
};

const results = {
  noShow: false,
  immediateAvailability: false,
  experience: {
    angular: false,
    react: false,
    vue: true,
  },
};

const status = interviewStatus(results);

console.log(status);

function interviewStatus(results) {
  return maybeReject(results) ?? maybeAccept(results) ?? STATUS.reevaluate;
}

// "maybe" prefix - returns a value or `undefiend`
function maybeReject({ noShow }) {
  return noShow ? STATUS.reject : undefined;
}

function maybeAccept({
  experience: { angular, react, vue },
  immediateAvailability,
}) {
  return angular || react || (vue && immediateAvailability)
    ? STATUS.accept
    : undefined;
}
Enter fullscreen mode Exit fullscreen mode

In my view:

  • the rejection criteria can be easily located and identified quickly
  • the acceptance criteria can be easily located and identified fairly quickly
  • it should be obvious that rejection criteria take precedence over acceptance criteria

Now I'm certain there already is a queue of people forming getting ready to drop a ton of bricks on me, how they don't find the above code readable at all.

That's not surprising β€” something is only judged as "readable" if it is already familiar. Anything unfamilar to an individual is also not as readable because of the additional cognitive effort that is required to read it.

In natural langauge there is the notion that

If you learn only 800 of the most frequently-used lemmas in English, you'll be able to understand 75% of the language as it is spoken in normal life.

However

Eight hundred lemmas will help you speak a language in a day-to-day setting, but to understand dialogue in film or TV you'll need to know the 3,000 most common lemmas.

Going further

And if you want to get your head around the written word - so novels, newspapers, excellently-written … articles - you need to learn 8,000 to 9,000 lemmas.

So just because something isn't deemed "readable" by a certain group of people doesn't necessarily imply that it is unreasonable.

From that point of view it's important to get exposure to a broad spectrum of programming styles (preferably across languages from different programming paradigms) β€” with that exposure a lot more code becomes "readable".