# Equivalence

An `Equivalence<A>` is a function `(a: A, b: A) => boolean` that decides when two
values should be treated as *the same* — for some specific purpose. Unlike
[`Equal`](https://effect.plants.sh/traits/equal-and-hash/), which is the single canonical structural
equality used throughout Effect, you can define **many** equivalences for one
type: compare strings case-insensitively, dates by their timestamp, or users by
their `id` alone.

Every equivalence must be reflexive (`eq(a, a)` is `true`), symmetric, and
transitive. Many Effect APIs accept an `Equivalence` — `Array.dedupeWith`,
`Array.containsWith`, and others — so a custom equivalence is often all you need
to make a built-in operation behave the way your domain expects.

```ts
import { Array, Equivalence } from "effect"

// Two strings are "the same" if they match ignoring case
const caseInsensitive = Equivalence.make<string>(
  (a, b) => a.toLowerCase() === b.toLowerCase()
)

console.log(caseInsensitive("Hello", "HELLO")) // true

// Deduplicate using that notion of sameness
console.log(Array.dedupeWith(["Hello", "world", "HELLO", "World"], caseInsensitive))
// ["Hello", "world"]
```

`Equivalence.make` adds a fast reference-equality (`===`) check before calling
your function, so identical references short-circuit to `true`.

## Building blocks: `mapInput` and `combine`

Two combinators do most of the work. `mapInput` derives an equivalence for a
larger type by projecting out the part you care about; `combine` ANDs two
equivalences together so both must agree.

```ts
import { Equivalence } from "effect"

interface User {
  readonly id: number
  readonly name: string
  readonly email: string
}

// Compare users by id only — name and email are ignored
const byId = Equivalence.mapInput(
  Equivalence.strictEqual<number>(),
  (user: User) => user.id
)

const a = { id: 1, name: "Alice", email: "alice@example.com" }
const b = { id: 1, name: "Alice Smith", email: "alice@work.com" }
const c = { id: 2, name: "Bob", email: "bob@example.com" }

console.log(byId(a, b)) // true  (same id)
console.log(byId(a, c)) // false (different id)
```

`combine` chains two equivalences; both have to hold. The second is only checked
when the first returns `true` (short-circuiting):

```ts
import { Equivalence } from "effect"

interface User {
  readonly name: string
  readonly age: number
}

const byName = Equivalence.mapInput(
  Equivalence.strictEqual<string>(),
  (u: User) => u.name
)
const byAge = Equivalence.mapInput(
  Equivalence.strictEqual<number>(),
  (u: User) => u.age
)

// Equivalent only when BOTH name and age match
const sameUser = Equivalence.combine(byName, byAge)

console.log(sameUser({ name: "Alice", age: 30 }, { name: "Alice", age: 30 })) // true
console.log(sameUser({ name: "Alice", age: 30 }, { name: "Alice", age: 31 })) // false
```

## Structured equivalences

You rarely build struct equivalences field-by-field with `combine` — there are
direct combinators. `Equivalence.Struct` takes a per-field equivalence and only
compares the listed fields (extra fields are ignored). `Tuple`, `Array`, and
`Record` cover the other shapes.

```ts
import { Equivalence } from "effect"

interface Person {
  readonly name: string
  readonly age: number
  readonly email: string
}

// Names and emails compare case-insensitively; age must match exactly
const caseInsensitive = Equivalence.mapInput(
  Equivalence.strictEqual<string>(),
  (s: string) => s.toLowerCase()
)

const personEq = Equivalence.Struct({
  name: caseInsensitive,
  age: Equivalence.Number,
  email: caseInsensitive
})

const p1 = { name: "Alice", age: 30, email: "alice@example.com" }
const p2 = { name: "ALICE", age: 30, email: "ALICE@EXAMPLE.COM" }

console.log(personEq(p1, p2)) // true (different casing, same person)
```

The other structured combinators follow the same pattern:

```ts
import { Equivalence } from "effect"

// Tuple: a different equivalence per position
const point = Equivalence.Tuple([Equivalence.Number, Equivalence.Number])
console.log(point([1, 2], [1, 2])) // true

// Array: one equivalence for every element; lengths must match
const tags = Equivalence.Array(Equivalence.String)
console.log(tags(["a", "b"], ["a", "b"])) // true
console.log(tags(["a"], ["a", "b"])) // false (different length)

// Record: compares all keys; both objects need the same key set
const scores = Equivalence.Record(Equivalence.Number)
console.log(scores({ alice: 1 }, { alice: 1 })) // true
```
**Note:** The primitive instances differ in how they treat `NaN`. `Equivalence.Number`
treats `NaN` as equal to `NaN`, while `Equivalence.strictEqual()` uses raw `===`
and therefore reports `NaN` values as **not** equivalent.

## Deriving an equivalence from a Schema

If you already describe your data with [`Schema`](https://effect.plants.sh/schema/), you do not need to
assemble an equivalence by hand — `Schema.toEquivalence` derives one that matches
the schema's structure.

```ts
import { Schema } from "effect"

const User = Schema.Struct({
  id: Schema.Number,
  name: Schema.String
})

const eq = Schema.toEquivalence(User)

console.log(eq({ id: 1, name: "Alice" }, { id: 1, name: "Alice" })) // true
console.log(eq({ id: 1, name: "Alice" }, { id: 2, name: "Alice" })) // false
```

## Equivalence vs. Equal

Both answer "are these the same?", but they fill different roles:

- **`Equal`** is the *one* structural equality baked into Effect. Types
  implement it (or inherit it from `Data`), and hash-based collections rely on
  it. Use it when you want "same contents" without configuration.
- **`Equivalence`** is a value you build and pass in to define *situational*
  sameness for a specific operation. Use it when "the same" depends on context —
  ignoring case, comparing by a key, applying a numeric tolerance.

You can bridge from one to the other when needed: `Equal.asEquivalence()` wraps
the canonical structural equality as an `Equivalence` for APIs that expect one.

```ts
import { Array, Equal } from "effect"

const result = Array.dedupeWith([1, 2, 2, 3], Equal.asEquivalence<number>())
console.log(result) // [1, 2, 3]
```

## Equivalence reference

Everything the `Equivalence` module exports. The combinators and structured
helpers above are repeated here as compact entries so the whole module is
scannable in one place.

### `Equivalence<A>`

The core type: a function `(self: A, that: A) => boolean` that must be
reflexive, symmetric, and transitive. It is contravariant in `A`, so an
`Equivalence<unknown>` can be used wherever an `Equivalence<string>` is expected.

```ts
import type { Equivalence } from "effect"

const pointEq: Equivalence.Equivalence<{ x: number; y: number }> = (a, b) =>
  a.x === b.x && a.y === b.y

console.log(pointEq({ x: 1, y: 2 }, { x: 1, y: 2 })) // => true
console.log(pointEq({ x: 1, y: 2 }, { x: 9, y: 2 })) // => false
```

### `Equivalence.make`

Wraps a predicate as an `Equivalence`, adding a `===` fast path: identical
references return `true` without calling your function.

```ts
import { Equivalence } from "effect"

const tolerance = Equivalence.make<number>((a, b) => Math.abs(a - b) < 0.0001)

console.log(tolerance(1.0, 1.00001)) // => true
console.log(tolerance(1.0, 1.001)) // => false
```

### `Equivalence.strictEqual`

Builds an equivalence backed by raw `===`. Good for primitives and reference
equality; note `NaN` is never equal to itself and objects compare by reference.

```ts
import { Equivalence } from "effect"

const eq = Equivalence.strictEqual<number>()

console.log(eq(1, 1)) // => true
console.log(eq(NaN, NaN)) // => false  (NaN !== NaN)
```

### `Equivalence.String`

Built-in instance for strings using `===`.

```ts
import { Equivalence } from "effect"

console.log(Equivalence.String("hello", "hello")) // => true
console.log(Equivalence.String("hello", "world")) // => false
```

### `Equivalence.Number`

Built-in instance for numbers. Unlike `===`, it treats `NaN` as equal to `NaN`.

```ts
import { Equivalence } from "effect"

console.log(Equivalence.Number(1, 1)) // => true
console.log(Equivalence.Number(NaN, NaN)) // => true
```

### `Equivalence.Boolean`

Built-in instance for booleans using `===`.

```ts
import { Equivalence } from "effect"

console.log(Equivalence.Boolean(true, true)) // => true
console.log(Equivalence.Boolean(true, false)) // => false
```

### `Equivalence.BigInt`

Built-in instance for bigints using `===`.

```ts
import { Equivalence } from "effect"

console.log(Equivalence.BigInt(1n, 1n)) // => true
console.log(Equivalence.BigInt(1n, 2n)) // => false
```

### `Equivalence.Date`

Built-in instance for `Date` values. Compares by `getTime()` via
`Equivalence.Number`, so distinct instances with the same timestamp are equal,
and two invalid dates (both `NaN` time) are equal too.

```ts
import { Equivalence } from "effect"

const a = new Date("2020-01-01T00:00:00.000Z")
const b = new Date("2020-01-01T00:00:00.000Z")

console.log(a === b) // => false (different references)
console.log(Equivalence.Date(a, b)) // => true  (same time value)
console.log(Equivalence.Date(new Date("foo"), new Date("bar"))) // => true (both invalid)
```

### `Equivalence.combine`

ANDs two equivalences. The second is only evaluated when the first returns
`true` (short-circuiting).

```ts
import { Equivalence } from "effect"

const byLength = Equivalence.mapInput(Equivalence.Number, (s: string) => s.length)
const sameFirst = Equivalence.mapInput(Equivalence.String, (s: string) => s[0])

const eq = Equivalence.combine(byLength, sameFirst)

console.log(eq("cat", "cup")) // => true  (len 3, first "c")
console.log(eq("cat", "dog")) // => false (different first char)
```

### `Equivalence.combineAll`

ANDs an iterable of equivalences. An empty iterable yields an equivalence that
always returns `true`.

```ts
import { Equivalence } from "effect"

const eq = Equivalence.combineAll([Equivalence.Number, Equivalence.Number])
console.log(eq(1, 1)) // => true

const always = Equivalence.combineAll<number>([])
console.log(always(1, 2)) // => true  (empty collection)
```

### `Equivalence.mapInput`

Derives an equivalence for a new type by projecting each input before comparing.

```ts
import { Equivalence } from "effect"

const caseInsensitive = Equivalence.mapInput(
  Equivalence.String,
  (s: string) => s.toLowerCase()
)

console.log(caseInsensitive("Hello", "HELLO")) // => true
console.log(caseInsensitive("Hello", "World")) // => false
```

### `Equivalence.Tuple`

Builds an equivalence for fixed-length tuples, applying one equivalence per
position. Tuples of different lengths are never equivalent.

```ts
import { Equivalence } from "effect"

const eq = Equivalence.Tuple([Equivalence.String, Equivalence.Number])

console.log(eq(["a", 1], ["a", 1])) // => true
console.log(eq(["a", 1], ["a", 2])) // => false
```

### `Equivalence.Struct`

Builds an equivalence for objects from a per-field equivalence. Only the listed
fields are compared; extra properties are ignored. Symbol keys are supported.

```ts
import { Equivalence } from "effect"

const eq = Equivalence.Struct({
  name: Equivalence.String,
  age: Equivalence.Number
})

console.log(eq({ name: "Alice", age: 30, extra: 1 }, { name: "Alice", age: 30, extra: 2 })) // => true
console.log(eq({ name: "Alice", age: 30 }, { name: "Alice", age: 31 })) // => false
```

### `Equivalence.Record`

Builds an equivalence for records/dictionaries, comparing every value with the
same equivalence. Both objects must have the exact same set of keys.

```ts
import { Equivalence } from "effect"

const eq = Equivalence.Record(Equivalence.Number)

console.log(eq({ a: 1, b: 2 }, { a: 1, b: 2 })) // => true
console.log(eq({ a: 1, b: 2 }, { a: 1 })) // => false (different keys)
```

### `Equivalence.Array`

Builds an equivalence for arrays, comparing elements positionally with a single
equivalence. Lengths must match; empty arrays are equivalent.

```ts
import { Equivalence } from "effect"

const eq = Equivalence.Array(Equivalence.Number)

console.log(eq([1, 2, 3], [1, 2, 3])) // => true
console.log(eq([1, 2], [1, 2, 3])) // => false (different length)
console.log(eq([], [])) // => true
```

### `Equivalence.makeReducer`

Creates a `Reducer` of `Equivalence<A>` whose `combine`/`combineAll` AND
equivalences together, with an always-`true` identity. Useful when folding a
collection of equivalences.

```ts
import { Equivalence } from "effect"

const reducer = Equivalence.makeReducer<number>()

const combined = reducer.combineAll([
  Equivalence.Number,
  Equivalence.make<number>((a, b) => Math.abs(a - b) < 1)
])

console.log(combined(1, 1)) // => true
console.log(combined(1, 1.5)) // => false (strict number check fails)
console.log(reducer.initialValue(1, 2)) // => true  (identity is always-true)
```

### `Equivalence.EquivalenceTypeLambda`

The higher-kinded type lambda for `Equivalence`, used by Effect's HKT machinery.
You will rarely reference it directly in application code.

## See also

- [Equal & Hash](https://effect.plants.sh/traits/equal-and-hash/) — the canonical structural equality
  and its hashing contract.
- [Order](https://effect.plants.sh/traits/order/) — when you need an ordering, not just equality.
- [Schema](https://effect.plants.sh/schema/) — derive equivalences (and more) from your data definitions.