DEV Community

Cover image for Flow control, short-circuit expressions and functional programming in JavaScript
Osman Cea
Osman Cea

Posted on

Flow control, short-circuit expressions and functional programming in JavaScript

A couple of days ago while doing code review I saw a snippet like this:

React.useEffect(() => {
  someCondition && doSomething()
}, [someCondition, doSomething])
Enter fullscreen mode Exit fullscreen mode

You don't need to know what React.useEffect does or anything, I just want to focus on the body of the callback function.

I suggested my colleague to use an if statement instead of the short-circuit expression. When asked why it was preferable to use an if instead, I didn't have a reasonable answer. It just felt better to me.

But feeling better is not an acceptable reason, specially because it is highly subjective. What feels better to me won't necessarily feel better for the rest of the team.

So I just did what any other person would do: I obsessed about it (😅), trying to rationalize why it felt better.

Disclaimer: This article just describes my reasoning around this topic. Like it or not, that's completely up to you. I'm not suggesting this as a "best practice" or anything else.


One thing I learned from watching Rich Hickey's talks is to always start with a definition:

In computer science, an expression is a syntactic entity in a programming language that may be evaluated to determine its value.

Here's a bunch of expressions in JavaScript:

42                              // 42 
'foo'                           // 'foo'
false                           // false
const nums = [1, 2, 3]          // ??
nums                            // [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

Values in JavaScript evaluate to themselves, and variables holding values evaluate to whatever they hold. Notice the fourth line in the snippet above: in JavaScript assignments are also expressions. What do you think the expression const nums = [1, 2, 3] evaluates to?

Well, it evaluates to undefined.

In other programming languages (like Python) a variable assignment is not an expression, but a statement. Here's the definition for statement:

In computer programming, a statement is a syntactic unit of an imperative programming language that expresses some action to be carried out.

The important word here is action. Keep that in mind for now.

Here's a bunch of statements in JavaScript:

for (let n of nums) { /*...*/ }
while (true)        { /*...*/ }
if (nums.length)    { /*...*/ }
Enter fullscreen mode Exit fullscreen mode

Ignoring the fact that assignment is an expression (a useless expression, if I do say so myself) it would be reasonable to think that expressions are to values as statements are to actions.

Short-circuit evaluation

More definitions, yey:

Short-circuit evaluation, minimal evaluation, or McCarthy evaluation (after John McCarthy) is the semantics of some Boolean operators in some programming languages in which the second argument is executed or evaluated only if the first argument does not suffice to determine the value of the expression.

Here's an example:

true || false                 // true
Enter fullscreen mode Exit fullscreen mode

In the previous snippet of code, the right-hand side expression of the OR operator is not evaluated since the first argument is enough to determine the value of the whole expression.

It's kinda weird to think about it like this using literals, since literals evaluate to themselves. We'll write this differently so it's easier to reason about:

const aCondition = true
const anotherCondition = false

aCondition || anotherCondition   // true
Enter fullscreen mode Exit fullscreen mode

Since aCondition is true, there's no need for looking up the value of anotherCondition, whatever that is.

Let's try with another example:

const person = {
  get name() {
    console.log('Bayum!')
    return 'Bodoque'
  }
}

true || person.name           // true
Enter fullscreen mode Exit fullscreen mode

If you run this code, you'll notice 'Bayum!' is not logged to the console, since the left-hand side of the || operator is already true, which is good!

But what's the deal with this?

Side effects, functional programming & Haskell

We'll take a brief detour and continue with, guess what, another definition:

Haskell is a general-purpose, statically typed, purely functional programming language with type inference and lazy evaluation.

Let's write a little function with Haskell that prints "42" to the console:

doSomething = putStrLn "42"
Enter fullscreen mode Exit fullscreen mode

Using ghci, which is the Glasgow Haskell Compiler interactive environment (think of a REPL), we can check the type of our doSomething function:

Prelude> doSomething = putStrLn "42"
Prelude> :t doSomething 
doSomething :: IO ()
Enter fullscreen mode Exit fullscreen mode

doSomething is a function that takes no arguments and its return type is IO (), or IO of unit (an empty set of parentheses is called unit and it's similar to void in JavaScript). In Haskell all functions with side effects have a return type of IO of something. Pure functions can't call effectful functions. If you want to have a side effect, the return type should always be IO of something.

Although not mandatory, we can explicitly write type annotations:

doSomething :: IO ()
doSomething = putStrLn "42"

-- Here's another function that takes two Ints 
-- and returns another Int, just for contrast
add :: Int -> Int -> Int 
add a b = a + b
Enter fullscreen mode Exit fullscreen mode

Alright, detour is over, enough Haskell, let's get back on track.

Short-circuit expressions and flow control

A function invocation can always be replaced by its return value if it depends only in its inputs. Another way to phrase it, is that a function invocation can only be replaced by its return value if the function has no side effects.

This property is called referential transparency. Referentially transparent functions are also known as pure functions.

When doing functional programming, our goal is to maximize the surface area of code that's written with pure functions: they are easier to test and easier to reason about. So for most of your functions in a program, you're going to be interested in their return values:

const whatIsThis = someCondition && doSomething()
Enter fullscreen mode Exit fullscreen mode

If we're not interested about the result of doSomething, then it's probably worthless to store the value of the expression into whatIsThis, but the expression will still have a value, whether it is used or not:

function doSomething() {
  console.log("42")
}

someCondition && doSomething()   // `false` when `someCondition` is `false`
                                 // `undefined` when `someCondition` is `true`
Enter fullscreen mode Exit fullscreen mode

If we don't care about the value of the expression, then doSomething is most likely an effectful function. But JavaScript is no Haskell so there's no way to tell if doSomething is effectful or not without looking at its implementation. And even then, it wouldn't be necessarily something straightforward to figure out.

I think this is why I prefer to use an if statement instead of a short-circuit expression for flow control in effectful functions: for me it makes it completely unambiguous that we don't care about the return value, hence it's a side effect.

But what about effectful functions that DO return something?

We don't have a compiler like GHC to enforce purity in our functions, but we can still follow a similar convention that only effectful functions can call other effectful functions. Haskell does this using monads.

Instead of writing an explanation about this topic, let me point you to this really straightforward video that makes a wonderful job:

https://youtu.be/C2w45qRc3aU

Top comments (6)

Collapse
 
pengeszikra profile image
Peter Vivo

Imho I prefer ? : instead of &&, much more cleaner way to declare what get if someCondition is undefined | false | null | 0 | ""

const whatIsThis = someCondition ? doSomething() : null;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
daslaf profile image
Osman Cea

Yes, for sure, as long as you're interested in the result of the expression. But I've seen people do stuff like this, which is what I'm advocating againts:

function main() {
  someCondition
    ? doSomething()
    : doSomethingElse()
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
pengeszikra profile image
Peter Vivo

Yes you right. But what are you think about react reducer switch like this? :

const someReducer(state, {type, payload}) {
  switch (type) {
    case SET_SOME: return {...state, some: payload}
    case WORK_WITH_OTHER: return {...state, other: payload, some: false}
    // ... and so on
    default: return state;
  }
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
daslaf profile image
Osman Cea

Looks like a regular reducer to me... how is this related to what we were talking?

Thread Thread
 
pengeszikra profile image
Peter Vivo

reducer is looks same as ternary with two function, just case use one condition with many pure function.

Thread Thread
 
daslaf profile image
Osman Cea

Again, this is because you care about the result of the function. My take is: use statements for side effects and expressions for pure functions.