I posted an idea about IIFE in JavaScript, but wanted to write more. Executing functions immediately is a very special use of JavaScript. Since there was no mature modularization mechanism native to JavaScript in the past, immediate functions have been widely used to write separate modules and libraries to prevent global variable name conflicts. However, as the ES6 standard has improved its modularity and block-level scoping support, this usage has gradually faded into the background of code translated by Polyfill.

In discussions on the web, the immediate function function is basically used to isolate scopes and prevent global namespace conflicts. In practice, however, we tend to overlook its other function, which is the ability to convert any statement into expression.

The statement and expression

When we learned programming, we learned the concepts of statement and expression, but I did not care about these theoretical nouns at that time, because I thought it was intuitive to look at the code directly, and not discussing these nouns did not prevent me from writing the code. It wasn’t until I got more code experience, and exposed to the syntax of various languages and some strange tricks, that I gradually realized the importance of distinguishing between the two.

In order to write this article, I have googled the exact definition of statement and expression. However, there are different opinions on the Internet. Some regard them as two equivalent concepts, while others say that they contain relations. But despite all the details of definition, there is a consensus on the difference:

Expression is a piece of code that returns a value when executed, whereas statement simply executes a piece of logic and does not return a value.

In most languages, expression consists primarily of literal numbers, strings, and the operators that concatenate them, while statement contains assignment and control flow statements. Such as:

// expressions:
> 1 + 2
> "expression"
> Math.sqrt(2)
> x * x 

// statements:
> let x = 3;
> if () {}
> for () {}
> while () {} 
Copy the code

Convert statement to expression

Let’s focus on a common usage scenario:

switch (id) {
  case 1:
    this.name = (... some logic)break
  case 2:
    this.name = (... some other logic)break
  default:
    this.name = (... default logic)break
}
Copy the code

This code performs different logic depending on the value of the ID and assigns the calculated value to the this.name variable. A switch statement is a typical statement that goes into different branches depending on its value. But many times the logic of each branch is similar. For example, in the example above, the result of our calculation ends up being an assignment, which is repeated many times, causing code redundancy and inelegance.

Some languages support a syntax called Switch Expression, which, as the name implies, allows the switch statement to be an expression that returns a value after execution. The code rewritten with this syntax looks like :(pseudocode)

this.name = 
  switch (id) {
    case 1: (... some logic)case 2: (... some other logic)default: (... default logic) }Copy the code

And you can see it’s a lot simpler.

JavaScript doesn’t currently support this, but by executing the function immediately, we can work around it.

this.name = (() = > {
  switch (id) {
    case 1: return(... some logic)case 2: return(... some other logic)default: return(... default logic) } })()Copy the code

Since it is a function, not only switch statements, but also statements such as conditional judgments, loops, and even more complex logic can be converted to an expression in this way.

Does that make sense?

From a functional point of view, the original switch statement is fully functional, but the code redundancy is not severe and there is no obvious benefit. But from the point of view of code elegance and readability, it is better to rewrite it as expression.

A case in point is that Java added support for Switch Expression in JDK 12. As a widely used industrial-grade language, Java has not always pursued the most fashionable language features and is often criticized for its cumbersome syntax. The fact that this syntax is supported with such high priority in Java speaks volumes about its popularity among programmers.

Java 12 switch expression
var today = switch (day) {
  case SAT, SUN: break "Weekend day";
  case MON, TUS, WED, THU, FRI: break "Working day";
  default: throw new IllegalArgumentException("Invalid day: " + day.name());
};
Copy the code

In JavaScript, in addition to the same application scenarios as in other languages, there is another situation where expression encapsulated by the immediate function may be used, and that is the React JSX syntax. We often need to render JSX elements conditionally, but we can use this technique when mixing logic code into JSX that must contain expression.

<div>
  {(() = > {
    switch (name) {
      case "A": return <CompA />;
      case "B": return <CompB />;
      case "C": return <CompC />;
      default: return <CompA />
    }
  })()}
</div>
Copy the code

On a larger scale, it is really a question of whether any statement needs to be designed as expression. Separating code into statement and expression is not a natural design. For example, in functional programming, there are no statements, only expressions, and the concatenation of expressions makes the code more concise and fluid.

Some object-oriented languages have borrowed this idea, such as Ruby, where almost everything is expression, including class and method definitions. Even in traditional OO design, approaches like the Fluent Interface have sprung up to implement elegant streaming interface calls by rewriting setters to return context expression.

We’ll briefly describe several other ways to convert statement to expression in the following sections.

Why anonymous functions?

Going back to JavaScript, one might argue that this is nothing special, that you can extract the logic into a separate function to achieve the same effect.

function getNameById(id) {
  switch (id) {
    case 1: return(... some logic)case 2: return(... some other logic)default: return(... default logic) } }this.name = getNameById(id) 
Copy the code

Of course you can do that. In general, it is recommended to separate reusable logic into functions to reduce code duplication. But the finer the function, the better. If the logic is tightly context-dependent, it can be expensive to split, and context switches are required to read the code, reducing the readability of the code by understanding the correspondence between multiple parameters.

Function naming is also a common problem for developers.

In general, while the principle of using anonymous functions is the same as that of split functions, let’s not get too wrapped up in the nitty-gritty nature of it here, but instead take advantage of the concise syntax JavaScript provides and use it as a syntactic sugar. (Can be analogous to the same JavaScript!! Syntax, while nothing special in principle, shows up frequently in x little-known JavaScript tricks.)

Migrate to another language

Other languages don’t have JavaScript’s instant-execution function syntax, so can they use this idea to implement similar functionality? Not necessarily. Here we present a lower version of the Java language implementation:

final int id = 1;
String name = (new Callable<String>() {
  @Override
  public String call(a) {
    switch (id) {
      case 1: 
        return(... some logic);case 2: 
        return(... some other logic);default: 
        return (...default logic);
    }
  }
}).call();
Copy the code

We use anonymous inner classes in Java to convert statement to Expression. Callable is frankly not very common in Java and can be understood as a Runnable with the ability to return a value.

Compare it to the JavaScript version. In the Java version, the syntax of anonymous inner classes is relatively cumbersome. Although it can be further simplified by using Java 8’s lambda, it does not save the appearance of some method names and keywords, which can cause some visual interference in reading code. JavaScript’s instant-execution functions, along with the arrow functions, are all made up of symbols that we can easily omit when reading code and focus on the core logic.

In addition, when anonymous inner classes in Java refer to external variables, due to the restriction of Java execution mechanism, we must declare the external variables used as final, that is, immutable. This inevitably makes it more expensive and cumbersome to modify code in this way.

Third, Java’s anonymous inner classes become a separate class after compilation, and have class loading, memory, and other costs at runtime. Generally speaking, they are heavy, so they are generally not used in large numbers.

I don’t know if it’s done in any other language. It may not be as neat as JavaScript, but it’s still an interesting way to use it in your project if you’re interested.

Bonus: More on converting statement to Expression

Although this article focuses on the immediate use of functions, the purpose of “turning statement into expression” is itself a way to make code more elegant. In JavaScript, using a function that executes immediately is not the only way to achieve this.

Consider a common function to convert a configuration array to a key-value object:

const config = array.reduce((obj, item) = > {
  obj[item.prop] = item.value
  return obj
}, {})
Copy the code

It is easy to imagine that this can be done succinctly using the Reduce function, but the manual return obJ inside the Reduce function is an eyesore. The essence of this is that the assignment returns obj[item.prop], not obj itself.

So is there a way to simplify this code even further? One way to do this is to use an existing function, such as Object.assign or destruct assignment:

const config = array.reduce(
  (obj, item) = > Object.assign(obj, { [item.prop]: item.value }), 
  {}
)
Copy the code

Another approach is to resort to an even less popular syntax, comma expressions. We can concatenate a list of expressions with commas, and they will be executed from left to right, with the return value of the last expression as the return value of the whole. In this example, we just need:

const config = array.reduce(
  (obj, item) = > (obj[item.prop] = item.value, obj), 
  {}
)
Copy the code

But the syntax is a bit tricky and it’s not easy to see its purpose. All in all, we need to choose the appropriate writing method according to different scenarios.