iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
📘

Why '===' Differs: A Look at Language Design Through Equivalence and Identity

に公開

Introduction: The Uncanny Feeling of Talking Past Each Other Despite Using the Same Symbol

The === operator in JavaScript and the === operator in Kotlin look identical.
However, their meanings are completely different.

  • In JavaScript, === is a "strict equality comparison."
  • In Kotlin, === is a "referential identity comparison."

Many developers have likely felt that this difference is "confusing" or "hard to remember."
But this is neither a coincidence nor a design flaw.

Rather, this honestly reflects the differences in design philosophy regarding:

  • What the language considers safe.
  • At which layer the language attempts to prevent misunderstandings.

In this article, using the symbol === as our entry point, we will organize the two concepts of:

  • Equivalence
  • Identity

and structurally examine "why the meaning of comparison operators diverged across different languages."


Equivalence and Identity Are Different Questions Altogether

First, let's separate two questions that often get mixed up when talking about comparisons.

Equivalence: Are they the same in meaning?

Equivalence is the question: "Are they the same as values?"

  • The numbers 1 and 1
  • The strings "hello" and "hello"
  • Two data structures with the same content

These are considered equivalent if their meaning is the same, even if they are different entities.

Equivalence = Are they the same in "what they represent"?

This is the protagonist in most business logic and domain designs.


Identity: Are they the same entity?

On the other hand, identity is the question: "Are they really the same 'thing'?"

  • Are they the same object in memory?
  • Are they the same instance?
  • Do they share the same reference?

Identity = "Is it the same existence itself?"

This becomes critical in contexts like caching, mutable state, and lifecycle management.


What is important here is that:

Equivalence and identity are not about which is superior.

They simply have different roles and are needed in different situations.


=== in JavaScript: Eliminating "Discrepancies in Meaning" First

When discussing JavaScript comparison operators, == inevitably takes the heat.

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

While "flexible," this poses the problem of implicitly jumping between layers of comparison.

The Boundary Introduced by ===

To address this, JavaScript introduced === as a comparison that does not perform type coercion.

0 === false // false

What === is trying to protect here is:

  • Not equivalence itself,
  • But rather "consistency of interpretation that is not intended."

In JavaScript, === is an operator that guarantees:

"Can I treat these two as having the same 'meaning'?"

while preventing the language side from injecting any extra interpretation.

In other words, === in JavaScript can be said to be:

  • A form of equivalence comparison,
  • But an "equivalence that prohibits implicit correction."

=== in Kotlin: Forcing a Separation Between Equivalence and Identity

On the other hand, in Kotlin, the comparison operators are separated from the start.

a == b   // Equivalence comparison via equals
a === b  // Referential identity comparison

The point here is a strong will to:

Not allow equivalence and identity to be mixed at the symbol level.

Why did Kotlin separate them?

Kotlin (and Java-based languages) are designed with the following as prerequisites:

  • Objects
  • Mutable state
  • Explicit lifecycles

In this world, confusing "same as a value" with "same instance" allows bugs to creep in silently.

Therefore, Kotlin adopted a design where it forces developers to choose every time:

  • == → "Are they the same in meaning?"
  • === → "Are they the same entity?"

This design is less about being helpful and more about coercion to prevent mistakes.


Different Meanings for the Same Symbol Arise from Different Priorities in Protection

Looking at this so far, the === in JavaScript and the === in Kotlin are trying to solve entirely different problems.

Language What === protects
JavaScript Misunderstandings caused by implicit type coercion
Kotlin Confusion between equivalence and identity

Even when using the same symbol, it is natural for the meanings to diverge if the following differ:

  • The anticipated failures
  • The historical background
  • The types of bugs prevalent in their respective battlefields

A Brief Peek at Other Languages Reveals the Axes of Design

  • Java

    • ==: Reference comparison
    • equals(): Equivalence comparison
  • Swift

    • ==: Equivalence comparison via Equatable
    • ===: Reference identity for classes
  • Python

    • ==: Equivalence
    • is: Identity

Every language eventually arrives at the same two questions. What differs are the design priorities regarding:

  • Which to make the default
  • Where to require explicitness
  • Where to prevent accidents

Conclusion: Comparison Operators Are a Gateway to Philosophy

The difference in === is not a matter to be settled by memorizing symbols.

It is packed with a philosophy regarding:

  • What kinds of mistakes this language has observed most frequently
  • What kind of judgment it wants to entrust to developers
  • To what extent the language itself should take responsibility

Discussion