Allow Prefixing Conditional Blocks w/ "do"

Here is a redux code snippet...

function reducer(count = 0, { type }) {
  switch (type) {
    case "increment": return count + 1;
    case "decrement": return count - 1;
    default: return count;
  }
}

Aren't those redundant return keywords ugly? One way to solve this is by using do expressions.

function reducer(count = 0, { type }) {
  return do {
    switch (type) {
      case "increment": count + 1;
      case "decrement": count - 1;
      default: count;
    }
  }
}

This is great, but it intros an extra level of nesting in our code. What if we can prefix switch statements with a do like this...?

function reducer(count = 0, { type }) {
  return do switch (type) {
    case "increment": count + 1;
    case "decrement": count - 1;
    default: count;
  }
}

This can also be done for if/else or if/else if * n/else. So we can change this...

return (
  <nav>
    <Home />
    {
      do {
        if  (user.loggedIn) <SigninButton />
        else <LogoutButton />
      }
    }
  </nav>
)

into something like this...

return (
  <nav>
    <Home />
    {
      do if (user.loggedIn) <SigninButton />
      else <LogoutButton />
    }
  </nav>
)

Let me know what you think. Thanks! :grinning:

Your specific use case for switch statements can be solved by the upcoming pattern matching proposal.

function reducer(count = 0, { type }) {
  return match (type) {
    when ("increment") { count + 1; }
    when ("decrement") { count - 1; }
    else { count; }
  }
}

As for the general idea of being allowed to prefix a statement with do as a shorthand, there's an open conversation around that already going on over here. Feel free to contribute to it!

3 Likes

Indeed, pattern matching is what you want. But the reason that’s ugly is the switch, not the returns.

2 Likes

i don't think the original code you're trying to "prettify" is ugly at all.

it has good readability, and less-confusing than "do" when you inevitably need to add more features to it, e.g.

 function reducer(count = 0, { type }) {
   switch (type) {
     case "increment":
       return count + 1;
     case "decrement":
-      return count - 1;
+      // future-proof when need arises to extend the case-statement, e.g.:
+      count -= 1;
+      if (count === 0) {
+          delete referenceDict[foo];
+      }
+      return count;
     default:
       return count;
   }
 }

Maybe I'm too old, but I don't get the way that code is written at all. A return statement shouldn't be in the middle of a function. Do the calculation first, then return. That also makes the swtich cleaner (not that swtich is a good choice for only 3 cases).

function reducer(count = 0, { type }) {
   if (type == "increment") ++count;
   else if (type == "decrement") --count;
   return count;
}

Much less code. Much more readable. Easier to maintain. No need for new syntax. Single return statement.

I get where you're coming from, but functional programming idiomatically prefers returns over mutation, and I've for years seen both procedural OO and functional idioms used in JS in the wild. And Kai's style here aligns more with the (impure) functional paradigm, not procedural object-oriented.

And of course, YMMV - I've personally found the return variant easier to understand and maintain as it's a little more visually declarative.

2 Likes

Honestly, that's a minor quibble. FP is just procedural with a (pointless?) extra restriction.

function reducer(count = 0, { type }) {
   let retval = count;
   if (type == "increment") ++retval;
   else if (type == "decrement") --retval;
   return reval;
}

Same thing rewritten as a pure function. What do you gain from doing that? Under the presumption that count is always a number, it is already a copy of the original data. The original data is therefore never mutated even in the original form.

I'm not quite sure how that's an argument against @claudiameadows's point. Technically, both of your functions are pure from an external viewer's perspective, in that they'll never modify the input arguments or perform side effects (using ++ on an argument won't mutate the value the caller holds), but @ethanlal04's original code snippet still leans more heavily on the functional spectrum, because it also avoids mutating local variables (FP is more than pure functions, just like OOP is more than grouping functions and data). And, there are many people out there who prefer reading and writing code that's more akin to what @ethanlal04 wrote.

I do have to agree with @claudiameadows as well that, despite the extra verbosity, I find that original code snippet a little easier to read, because it's a little more declarative in nature, but of course, "ease of readability" is such a subjective measurement that in large part depends on the type of code you're used to reading, and it's certainly understandable if you don't feel the same way there.

2 Likes

I understand the push towards FP and will not begrudge anyone for choosing to dogmatically follow that path. Frankly from my perspective, it's the difference between using a flat ratchet and a ratcheting screwdriver: of no meritable difference unless there's a useful advantage in using it. From this perspective, adding more language features to make more ergonomic something that already exists in an ergonomic form using one of many other approaches doesn't make much sense.

Given that, I'll back off because I don't believe I have anything constructive to add.