Proposal: Boolean Chaining

Proposal for Boolean Chaining Syntax in JavaScript

Introduction

This proposal introduces a new syntax for JavaScript called "Boolean Chaining". This syntax simplifies inline boolean checks and expressions by introducing a colon : operator. The purpose of the colon operator is to "cap" the return data, meaning everything after the colon is used purely for boolean check.

This feature aims to make JavaScript code more readable and concise, reducing the complexity of nested ternary and logical expressions, and decreasing the need for intermediate variables or repeated object property access.

Syntax

The syntax introduces a colon : operator in a property access chain. Here's an example:

const obj = { prop: [1, 2, 3, 4, 5] };

obj.prop:.arr[4] > 3
/* condition (5 > 3) is truthy, so `obj.prop` is returned */

The expression obj.prop:.arr[4] > 3 will perform a boolean check on obj.prop.arr[4] > 3 and if true, will return obj.prop. If false, it returns undefined.

The boolean chaining can also be used with fallback expressions, e.g.:

const obj = { prop: [1, 2, 3, 4, 5] };
const fallbackValue = 1;

obj.prop:.arr[4] < 2 || fallbackValue
/* condition (5 < 2) is falsy, so `false` is returned,
   then falls back to `fallbackValue` */

If the condition obj.prop.arr[4] > 3 is false, fallbackValue is returned.

Unambiguity with Ternary Expressions

While : is also used in ternary expressions, the new syntax wouldn't conflict with them due to the existing constraints on ternary operator syntax. In a ternary expression, the : is always preceded by **?, forming the ? : pair. In our proposal, the : is always preceded by an identifier or a property access, making these two usages unambiguous.

Examples

Here are some more examples to illustrate the usage of Boolean chaining:

1. Nested Ternary Operations

Sample data:

const obj = { prop: [1, 2, 3, 4, 5] },
const otherValue = [17];
const fallbackValue = 1;

Current syntax:

obj.prop.arr[4] < 2 ? obj.prop : otherValue[0] > 3 ? otherValue : fallbackValue

With Boolean Chaining:

obj.prop:.arr[4] < 2 || otherValue:[0] > 3 || fallbackValue
/* 1st condition (5 < 2) is falsy, so `false is returned` is returned
   2nd condition (17 > 3) is truthy, so `otherValue` (`[17]`) is returned */

This example demonstrates chaining with the colon operator, which removes the need for nested ternary expressions.

2. Length Check

Current syntax:

arr?.length ? arr : []

With Boolean Chaining:

arr:?.length ?? []

The example illustrates how Boolean Chaining simplifies syntax when checking for property existence before its usage.

3. Plain Variable Check

With the proposed Boolean Chaining syntax, you can easily perform a boolean check on a variable and return the variable itself if the condition is true. This can be useful in scenarios where you want to return a primitive variable conditionally without resorting to ternaries for this one edge case.

Current syntax:

let num = 5;
num > 3 ? num : 0

With Boolean Chaining:

let num = 5;
num: > 3 || 0

In this example, the expression num: > 3 will check if num > 3 and if true, will return num. If num > 3 is false, it returns 0.

4. Usage in a Ternary Expression

The Boolean Chaining syntax can also be used within a ternary expression. The two uses of : do not conflict because the use in a ternary operator always follows a ?.

Current syntax:

let value = 5;
let condition = true;
condition ? value > 3 ? value : 0 : 0

With Boolean Chaining:

let value = 5;
let condition = true;
condition ? value: > 3 || 0 : 0

In this example, if condition is true, it checks value > 3 and returns value if the check is true, else it returns 0. If condition is false, it directly returns 0. Despite the presence of a ternary operator and the Boolean Chaining syntax, there is no conflict as they have distinct precedents.

This example is really just here ti demonstrate that there is no syntax conflict here. In a real-world use case, this would likely be more simply written like this:

condition && value: > 3 || 0

which is identical in structure to this:

condition && value > 3 && value || 

5. Multiple Checks in a Property Chain

The Boolean Chaining syntax can be used multiple times in a property chain, each performing a different check on the variable. Here is an example:

const obj = {
  prop1: {
    arr: [1, 2, 3, 4, 5],
    value: 4
  },
  prop2: {
    value: 3
  }
};

obj.prop1:(.arr[4] > 3):.value > 2 || obj.prop2:.value > 2 || 'fallback'

In this code, the expression first checks if obj.prop1.arr[4] is greater than 3. If true, it proceeds to check if obj.prop1.value is greater than 2. If both conditions are met, obj.prop1 is returned. If not, the expression checks if obj.prop2.value is greater than 2. If true, obj.prop2 is returned; otherwise, the string 'fallback' is returned.

Notice that I wrapped the first (.arr[4] > 3) condition in parentheses to remove ambiguity between the two groups vs the second group being applied to 3 itself, as you might sometimes want to use Boolean Chaining in the condition itself.

6. Using a Function as the Condition

:warning: I'm on the fence with this one. I can certainly see extra value in being able to do functional checks here as well, but it feels like this may be pushing the envelope in terms of sophistication and syntax readability.

The Boolean Chaining syntax can also be used with functions.

function longerThanThree(arr) {
  return arr.length > 3;
}

obj?.prop:longerThanThree

In this example, the function isLengthGreaterThanThree is applied to obj.prop. If the length of obj.prop (which is an array) is greater than 3, the function returns true and the expression obj.prop::isLengthGreaterThanThree returns obj.prop. If the condition is not met, it returns false without any fallback value.

The equivalent of this would be:

Boolean(isLengthGreaterThanThree(obj?.prop)) && obj?.prop

On the surface, this may feel a bit familiar to the :: binding syntax, but its usage is different here as it is used solely for a boolean check on the right-hand side of Boolean Chaining syntax.

This would also support inlining functions, like this:

obj?.prop:(x => x.length > 3)

Though, for something as simple as a length check like this, a simple obj?.prop:.length > 3 would suffice.

Motivation and Benefits

  1. Readability: With the new syntax, code becomes more readable as we can directly see the conditions associated with the use of variables.

  2. Conciseness: The new syntax reduces verbosity by avoiding repetition of object property access or variable names.

  3. Chaining: This proposal allows chaining of boolean checks, thus avoiding nested ternary hell and complex logical operations.

  4. Performance: It potentially improves performance by reducing the need for accessing deep properties multiple times.

Possible Concerns & Other Considerations

  1. Falsy return value: I'm thinking through whether it would be more sensible to always return false when falsy or just return the falsy value. Most of the examples above use the former, but I'm actually leaning towards the latter now.

  2. Learning Curve: The new syntax introduces a learning curve and may lead to confusion for beginners.

  3. Backward Compatibility: The proposal might have compatibility issues with older JavaScript engines if not transpiled.

  4. Possible syntax conflicts:

    • Ternary expressions: I addressed this a bit already in the brief above, and I don't believe there to be any syntax conflicts, but I may be overlooking some syntax.
    • TypeScript: I've considered that the syntax used in the "Plain Variable Check" example looks similar to TS. I don't think there would actually be a conflict as TS uses the : after variable names when initializing variables, where that would never be a place for Boolean Chaining syntax, except after the = where TS typing has concluded.
  5. Name and symbol: We can always bikeshed on the exact name and symbols used later on, but I believe there's a need for this regardless of what syntax is used.

Conclusion

In summary, the proposed Boolean Chaining syntax in JavaScript aims to improve the readability and conciseness of the language, simplify boolean checks, and potentially enhance performance. It is a significant step toward writing cleaner, more maintainable JavaScript code.