Coersion

·

13 min read

Coercion aka 'type conversion' is a mechanism of converting one type to another. In statically (strongly) typed language this process happens at compile time whereas coercion is a run-time conversion for dynamically typed languages

Coercion always results in either strings, numbers, or booleans. Understanding coercion will help you avoid problems that can occur in your code.

Abstract Operations

These operations are not a part of the ECMAScript language; they are defined here to solely to aid the specification of the semantics of the ECMAScript language. Other, more specialized abstract operations are defined throughout this specification.

Type Conversion
(in JavaScript type conversion is referred to as coersion)

The ECMAScript language implicitly performs automatic type conversion as needed. To clarify the semantics of certain constructs it is useful to define a set of conversion abstract operations. The conversion abstract operations are polymorphic; they can accept a value of any ECMAScript language type. But no other specification types are used with these operations. (aka "coercion").

toPrimitive:

toPrimitive is our first abstract operation in JavaScript that converts an input argument to a non-Object type. It is not an actual function in javascript but it is a set of code, a method. It takes an input argument and an optional argument PreferredType. If an object is capable of converting to more than one primitive type, it may use the optional hint PreferredType to favor that type.

  1. If PreferredType is not passed in, set the hint to “default”. (PreferredType indicates what primitive type we are trying to convert to. hint is like a local variable used in the algorithm.)

  2. Else if PreferredType is String, then set hint to “string". the primitive value gets checked by using toString() first and then valueOf().

  3. Else if PreferredType is Number, then set hint to "number”. the primitive value gets checked this by valueOf() first and then toString().

  4. If there is a method on the input object with the key Symbol.toPrimitive, then:1. Call that method and pass the hint as the first parameter.

    2. If the return value of this method is of non-Object type, then return that value. Else throw TypeError.

  5. (If we are on this step, it means there was no custom Symbol.toPrimitive method.)
    If hint is “default”, set hint to “number”. (This is an important step in the algorithm. ToPrimitive algorithm favors converting to number if the hint is not passed or if the hint is “default”.)

  6. Return the result of calling OrdinaryToPrimitive(input, hint).

algorithms in JavaScript are inherently recursive they get invoked again and again till we get our result or an error.

toString:
So what happens if there's no [Symbol.toPrimitive] method on the Object, or we're working in a pre-ES6 environment that doesn't recognize [Symbol.toPrimitive]? The old school way of converting a JavaScript Object to a String value as defined in the ToPrimitive Abstract Operation is to look for a .toString() method.

By default, all Objects have a generic .toString() method they inherit from Object.prototype.toString(). If no other .toString() method is found on the Object, then this generic one will get called, which returns the internal Class property, resulting in a frequently seen (but seldom helpful) "[object Object]":

TypeString Representation
null"null"
undefined"undefined"
Boolean (example: true)"true"
Boolean (example: false)"false"
Number (example: 100)"100"
Really big Number (example: 1230000000000000000000)That number in exponent form (example: "1.23e+21")
Really small Number (example: .00000000000000123)That number in exponent form (example: "1.23e-15")
Object with a defined [Symbol.toPrimitive] methodThe value returned by the defined [Symbol.toPrimitive] for hint === "string"
Object with a defined .toString() methodThe value returned by the defined .toString() method
Object without defined [Symbol.toPrimitive] or .toString() methods"[object Object]"

toNumbers:
Whenever we perform a numeric operation, and one or both operands aren't numbers, the ToNumber() abstract operation will be invoked to convert it to a value of type number. many corner cases involved in the toNumber operation. Let's see some examples:

TypeNumber Representation
null0
undefinedNaN
Boolean (true)1
Boolean (false)0
String (example: "100")100
String (example: "hello")NaN
Object with a defined [Symbol.toPrimitive] methodThe value returned by the defined [Symbol.toPrimitive] for hint === "number"
Object with a defined .valueOf() methodThe value returned by the defined .valueOf() method
Object without defined [Symbol.toPrimitive] or .valueOf() methodsNaN

As seen from the table above, when ToNumber() is called on a non-primitive (any of the object types) value, it is first converted to its primitive equivalent by invoking ToPrimitive() abstract operation and passing number as the PreferredType hint.
The return value from the ToPrimitive() operation will then be coerced into a number by the ToNumber() abstract operation. If it still doesn't result in a primitive value, it throws an error.

toBoolean:

The abstract operation ToBoolean() is called to convert an argument to a Boolean type whenever we use a value that is not Boolean in a place that needs a Boolean. The ToBoolean() abstract operation does not invoke the ToPrimitive() or any of the other abstract operations.
It just checks to see if the value is either falsy or not. There is a lookup table in the spec that defines a list of values that will return false when coerced to a boolean. They are called falsy values.

TypeBoolean Representation
nullfalse
undefinedfalse
0, +0, or -0false
NaNfalse
"" (empty String)false
All other values and typestrue

(->coersion over triple=??/)

Boxing:
A form of implicit coercion. JavaScript implicitly coerces the primitives into their object counterparts, so that we can access properties and methods on them.

Boxing and coercion are different things, that can happen independently, one or the other, or both of them.

  • Boxing is wrapping a primitive inside an Object.

  • Coercion is like interpreting a primitive as a different type.

Philosophy of coersion:

You cant change the mechanism of your code just to avoid the corner cases so what we do is adapt coding styles that makes our types and the values in those types plain and obvious. For example why not design a function that takes only takes strings or only numbers or both and make it clear that it only has two types so that we know the exact corner cases. It is within our codes purview of how much or how little we get affected by those facts (corner cases). It is not suggested that the only solution to our problem is that everything is completely and statically typed.

Do not think that these typing systems are weaknesses, actually think this is one of JavaScript's strongest qualities.

It's an unsung hero, I think it's one of the reasons why JavaScript is the ubiquitous language that it is today because it has been so palatable to so many different use cases. The first truly multi-paradigm language and a big reason why it has been able to survive multi-paradigm is because of its type system. And that's completely opposite from everything else that you've ever heard.

Corner Cases in Type Coercion

1. Addition vs. Concatenation

In JavaScript, the + operator can be used both for addition and string concatenation. This can lead to surprising results:

1 + 2 // 3 (addition)
"1" + 2 // "12" (concatenation)

In the second example, JavaScript implicitly coerces the number 2 to a string and performs string concatenation.

2. Truthy and Falsy Values

JavaScript has a concept of "truthy" and "falsy" values. While true and false are obvious Boolean values, other types also have truthy and falsy representations:

if (1) {
  // This block will execute because 1 is truthy
}

if (0) {
  // This block will not execute because 0 is falsy
}

if ("Hello") {
  // This block will execute because non-empty strings are truthy
}

if ("") {
  // This block will not execute because empty strings are falsy
}

Understanding these coercions is essential when writing conditionals in JavaScript.

3. Loose Equality (==) Pitfalls

The loose equality operator (==) can lead to unexpected results due to type coercion:

0 == false // true
"" == false // true
null == undefined // true

It's crucial to be aware of these behaviours when using the == operator.

4. NaN Comparisons

Comparing NaN using the equality operator can be tricky:

NaN == NaN // false

To check for NaN, you should use the isNaN() function.

5. Type Coercion in Conditional Statements

Type coercion can affect the outcome of conditional statements:

const x = "5";

if (x == 5) {
  // This block will execute
}

if (x === 5) {
  // This block will not execute due to strict equality
}

When writing conditionals, be mindful of whether you want strict or loose equality.

Equality:

JavaScript is a versatile and widely-used programming language that provides developers with a multitude of tools to work with data and manipulate it. One of the most fundamental operations in JavaScript is comparing values, and this is where the concepts of double equals (==) and triple equals (===) come into play. In this blog post, we will explore the differences between these two equality operators and when to use them in your JavaScript code.

Double Equals (==)

The double equals operator, denoted as ==, is used for loose equality comparisons in JavaScript. When you use double equals to compare two values, JavaScript performs type coercion to make the values on both sides of the operator of the same type, if possible. Type coercion is the process of converting one data type to another to make them compatible for comparison.

Here's an example:

5 == "5" // true

In the above example, JavaScript converts the string "5" to a number before making the comparison. This can be convenient in some cases, but it can also lead to unexpected results if you're not careful.

In the above example, JavaScript converts the string "5" to a number before making the comparison. This can be convenient in some cases, but it can also lead to unexpected results if you're not careful.

Common Pitfalls with Double Equals

  1. Type Coercion: Type coercion can sometimes lead to unintended results. For example:

     0 == false // true
    

    In this case, JavaScript converts false to 0 before making the comparison, which may not be what you intended.

  2. NaN Comparisons: NaN (Not-a-Number) is a special value in JavaScript, and double equals does not behave as expected when comparing NaN values:

     NaN == NaN // false
    
  3. Inconsistent Behavior: Double equals can sometimes lead to unpredictable results when comparing different data types.

Triple Equals (===)

The triple equals operator, denoted as ===, is used for strict equality comparisons in JavaScript. Unlike double equals, triple equals does not perform type coercion. Instead, it checks whether the values on both sides of the operator are not only equal but also of the same data type.

Here's an example:

5 === "5" // false

In this case, the comparison returns false because the types are different: number and string. Triple equals is often recommended in JavaScript best practices because it helps avoid unexpected type coercion issues and ensures more predictable behavior.

Benifits of triple equals:

  1. Predictable Comparisons: With triple equals, you can be sure that you're comparing values of the same type, making your code more predictable and less error-prone.

  2. Avoid Type Coercion Bugs: Triple equals helps you catch potential bugs that might otherwise go unnoticed when using double equals.

  3. Explicit Code: Using triple equals makes your code more explicit, which can improve readability and maintainability.

->When to use each operator:

In general, it's a good practice to use the triple equals operator (===) in most cases because it provides strict and predictable comparisons. Use double equals (==) only when you explicitly need type coercion and understand its implications.

Here are some guidelines:

  • Use === when you want to compare values for strict equality without type coercion.

  • Use == when you explicitly want type coercion to be applied, and you understand how it works.

  • When comparing against null or undefined, it's generally safe to use == because these values are loosely equal to each other and to no other value.

  • When comparing against NaN, always use a special isNaN function or check if the value is NaN using !==.

Static Typing:

JavaScript, traditionally a dynamically-typed language, has seen significant advancements in recent years with the introduction of static typing. Static typing, a feature borrowed from statically-typed languages like Java or C++, has gained popularity in JavaScript through tools like TypeScript and Flow.

In a dynamically-typed language like traditional JavaScript, variable types are determined at runtime. This means you can change the type of a variable during the execution of a program. While this flexibility can be convenient, it can also lead to subtle bugs and make it challenging to maintain large codebases.

Static typing, on the other hand, enforces type constraints at compile-time. In a statically-typed language or a statically-typed subset of a language like TypeScript, variable types are declared explicitly, and type checking is done before the code is executed. This can catch many type-related errors before they ever reach a production environment.

Benefits of Static Typing in JavaScript

  1. Early Error Detection : Static typing helps catch type-related errors during development, reducing the likelihood of runtime errors.

  2. Improved Code Quality : By explicitly defining types, you make your code more self-documenting, which aids in code comprehension and maintenance.

  3. Enhanced Tooling Support : Static typing allows for better code analysis and intelligent autocompletion in code editors, leading to increased productivity.

  4. Scalability : Static typing is particularly valuable for large codebases and collaborative projects, as it prevents type-related bugs that can become hard to manage as code complexity grows.

TypeScript:
Developed by Microsoft, is a powerful and widely adopted tool for adding static typing to JavaScript.

Key Features:

  1. Strong Typing: TypeScript enforces strict typing rules. You explicitly define types for variables and function parameters, making it a strongly typed language.

  2. Superset of JavaScript: TypeScript is a superset of JavaScript, meaning that you can gradually adopt it in your project without rewriting existing JavaScript code.

  3. Rich Ecosystem: TypeScript has extensive support from the community, a wide range of type definitions for popular libraries, and seamless integration with popular code editors like Visual Studio Code.

  4. Advanced Tooling: TypeScript offers advanced features like type inference, autocompletion, and navigation within your code editor, improving developer productivity.

  5. Compilation Step: TypeScript code is compiled into JavaScript before execution, ensuring runtime compatibility with all environments.

  6. Explicit Configuration: TypeScript uses a tsconfig.json file for configuring compiler options and project settings.

Flow:
Developed by Facebook, is another tool for adding static typing to JavaScript, known for its gradual typing approach.

Key Features:

  1. Gradual Typing: Flow offers gradual typing, allowing you to selectively add type annotations to your JavaScript code. This makes it easy to adopt static typing incrementally.

  2. Inline Annotations: Flow uses inline type annotations with special comments, which means your codebase can look more like traditional JavaScript.

  3. Simplicity: Flow may have a smaller learning curve compared to TypeScript, making it a good choice for projects where you want to introduce type checking gradually.

  4. Minimal Tooling: Flow has fewer tooling options compared to TypeScript but still integrates well with popular code editors.

  5. No Compilation Required: Flow doesn't require a separate compilation step; it can be used directly with JavaScript code.

  6. Configuration Files: Flow uses .flowconfig files for configuring type checking.

Choosing Between TypeScript and Flow

The choice between TypeScript and Flow depends on your project's specific needs and your team's preferences:

  • TypeScript is an excellent choice if you want a mature, strongly typed language with a rich ecosystem and strong tooling support. It's well-suited for building large-scale applications.

  • Flow is a good option if you prefer gradual typing and a simpler learning curve. It's useful for smaller projects or when transitioning from plain JavaScript to static typing.

TypeScript and Flow aim to help developers write safer and more maintainable JavaScript code. Your decision should align with your project's requirements, your team's familiarity with the tools, and your vision for code quality and maintainability. Whichever you choose, the adoption of static typing is a significant step towards building robust and error-resistant JavaScript applications.