1/**
2 * Defines reusable equality functions for values of the same type.
3 *
4 * An `Equivalence<A>` returns `true` when two `A` values should be treated as
5 * the same for a particular purpose. This module includes strict equality
6 * instances for primitive types, constructors for custom comparisons, and
7 * helpers for tuples, arrays, structs, records, dates, and values compared
8 * through a derived field.
9 *
10 * @since 2.0.0
11 */
12import { dual } from "./Function.ts"
13import type { TypeLambda } from "./HKT.ts"
14import * as Reducer from "./Reducer.ts"
15
16/**
17 * Represents an equivalence relation over type `A`.
18 *
19 * **When to use**
20 *
21 * Use as a type annotation when you accept or return an equivalence function.
22 *
23 * **Details**
24 *
25 * - Returns `boolean`: `true` if values are equivalent, `false` otherwise
26 * - Must satisfy reflexive, symmetric, and transitive properties
27 *
28 * **Example** (Defining simple number equivalence)
29 *
30 * ```ts import.meta.vitest
31 * import type { Equivalence } from "effect"
32 *
33 * const numberEq: Equivalence.Equivalence<number> = (a, b) => a === b
34 *
35 * numberEq(1, 1) // => true
36 * numberEq(1, 2) // => false
37 * ```
38 *
39 * **Example** (Defining custom object equivalence)
40 *
41 * ```ts import.meta.vitest
42 * import type { Equivalence } from "effect"
43 *
44 * interface Point {
45 * x: number
46 * y: number
47 * }
48 *
49 * const pointEq: Equivalence.Equivalence<Point> = (a, b) =>
50 * a.x === b.x && a.y === b.y
51 *
52 * pointEq({ x: 1, y: 2 }, { x: 1, y: 2 }) // => true
53 * ```
54 *
55 * @see {@link make}
56 * @see {@link strictEqual}
57 * @category models
58 * @since 2.0.0
59 */
60export type Equivalence<in A> = (self: A, that: A) => boolean
61
62/**
63 * Type lambda for `Equivalence`, used for higher-kinded type operations.
64 *
65 * **When to use**
66 *
67 * Use when you need to abstract over `Equivalence` in higher-kinded type code.
68 *
69 * **Details**
70 *
71 * - Enables `Equivalence` to work with the Effect type system's HKT infrastructure
72 * - Used internally for type-level computations and generic abstractions
73 *
74 * **Example** (Type-level usage)
75 *
76 * ```ts import.meta.vitest
77 * import type { Equivalence, HKT } from "effect"
78 *
79 * // Used internally for type-level computations
80 * type NumberEquivalence = HKT.Kind<
81 * Equivalence.EquivalenceTypeLambda,
82 * never,
83 * never,
84 * never,
85 * number
86 * >
87 * // Equivalent to: Equivalence.Equivalence<number>
88 * ```
89 *
90 * @see {@link Equivalence}
91 * @see {@link TypeLambda}
92 * @category utility types
93 * @since 2.0.0
94 */
95export interface EquivalenceTypeLambda extends TypeLambda {
96 readonly type: Equivalence<this["Target"]>
97}
98
99/**
100 * Creates a custom equivalence relation with an optimized reference equality check.
101 *
102 * **When to use**
103 *
104 * Use when you need an equality rule that the built-in instances and input
105 * mapping helpers cannot express, and you can provide a law-abiding comparison.
106 *
107 * **Details**
108 *
109 * The returned equivalence first checks reference equality (`===`) for
110 * performance. If the values are not the same reference, it falls back to the
111 * provided equivalence function, which must satisfy reflexive, symmetric, and
112 * transitive properties.
113 *
114 * **Example** (Case-insensitive string equivalence)
115 *
116 * ```ts import.meta.vitest
117 * import { Equivalence } from "effect"
118 *
119 * const caseInsensitive = Equivalence.make<string>((a, b) =>
120 * a.toLowerCase() === b.toLowerCase()
121 * )
122 *
123 * caseInsensitive("Hello", "HELLO") // => true
124 * caseInsensitive("foo", "bar") // => false
125 *
126 * // Same reference optimization
127 * const str = "test"
128 * caseInsensitive(str, str) // => true
129 * ```
130 *
131 * **Example** (Comparing numbers with tolerance)
132 *
133 * ```ts import.meta.vitest
134 * import { Equivalence } from "effect"
135 *
136 * const tolerance = Equivalence.make<number>((a, b) => Math.abs(a - b) < 0.0001)
137 *
138 * tolerance(1.0, 1.001) // => false
139 * tolerance(1.0, 1.00001) // => true
140 * ```
141 *
142 * @see {@link strictEqual}
143 * @see {@link mapInput}
144 * @category constructors
145 * @since 2.0.0
146 */
147export const make = <A>(isEquivalent: (self: A, that: A) => boolean): Equivalence<A> => (self: A, that: A): boolean =>
148 self === that || isEquivalent(self, that)
149
150const isStrictEquivalent = (x: unknown, y: unknown) => x === y
151
152/**
153 * Creates an equivalence relation that uses strict equality (`===`) to compare values.
154 *
155 * **When to use**
156 *
157 * Use when you need strict equality (`===`) as the comparison.
158 *
159 * **Details**
160 *
161 * Uses JavaScript's strict equality operator (`===`). Primitives compare by
162 * value. Objects compare by reference, so only the same object instance is
163 * equivalent. Use this as a building block for more complex equivalences via
164 * `mapInput` or `combine`.
165 *
166 * **Gotchas**
167 *
168 * `NaN !== NaN`, so `NaN` values are never considered equivalent.
169 *
170 * **Example** (Comparing primitive types)
171 *
172 * ```ts import.meta.vitest
173 * import { Equivalence } from "effect"
174 *
175 * const strictEq = Equivalence.strictEqual<number>()
176 *
177 * strictEq(1, 1) // => true
178 * strictEq(1, 2) // => false
179 * strictEq(NaN, NaN) // => false
180 * ```
181 *
182 * **Example** (Comparing objects by reference)
183 *
184 * ```ts import.meta.vitest
185 * import { Equivalence } from "effect"
186 *
187 * const obj = { value: 42 }
188 * const strictObjEq = Equivalence.strictEqual<typeof obj>()
189 *
190 * strictObjEq(obj, obj) // => true
191 * strictObjEq(obj, { value: 42 }) // => false
192 * ```
193 *
194 * @see {@link make}
195 * @see `Equal` for structural equality
196 * @category constructors
197 * @since 4.0.0
198 */
199export const strictEqual: <A>() => Equivalence<A> = () => isStrictEquivalent
200
201/**
202 * Equivalence instance for strings using strict equality (`===`).
203 *
204 * **When to use**
205 *
206 * Use when you need to supply case-sensitive string equality.
207 *
208 * **Example** (Comparing strings)
209 *
210 * ```ts import.meta.vitest
211 * import { Equivalence } from "effect"
212 *
213 * Equivalence.String("hello", "hello") // => true
214 * Equivalence.String("hello", "world") // => false
215 * ```
216 *
217 * @category instances
218 * @since 4.0.0
219 */
220export const String: Equivalence<string> = isStrictEquivalent
221
222/**
223 * Equivalence instance for numbers.
224 *
225 * **When to use**
226 *
227 * Use when you need numeric equality that treats NaN as equal to itself.
228 *
229 * **Example** (Comparing numbers)
230 *
231 * ```ts import.meta.vitest
232 * import { Equivalence } from "effect"
233 *
234 * Equivalence.Number(1, 1) // => true
235 * Equivalence.Number(1, 2) // => false
236 * Equivalence.Number(NaN, NaN) // => true
237 * ```
238 *
239 * @category instances
240 * @since 4.0.0
241 */
242export const Number: Equivalence<number> = make((self, that) =>
243 globalThis.Number.isNaN(self) && globalThis.Number.isNaN(that)
244)
245
246/**
247 * Equivalence instance for booleans using strict equality (`===`).
248 *
249 * **When to use**
250 *
251 * Use when you need to supply boolean equality.
252 *
253 * **Example** (Comparing booleans)
254 *
255 * ```ts import.meta.vitest
256 * import { Equivalence } from "effect"
257 *
258 * Equivalence.Boolean(true, true) // => true
259 * Equivalence.Boolean(true, false) // => false
260 * ```
261 *
262 * @category instances
263 * @since 4.0.0
264 */
265export const Boolean: Equivalence<boolean> = isStrictEquivalent
266
267/**
268 * Equivalence instance for bigints using strict equality (`===`).
269 *
270 * **When to use**
271 *
272 * Use when you need to supply bigint equality.
273 *
274 * **Example** (Comparing bigints)
275 *
276 * ```ts import.meta.vitest
277 * import { Equivalence } from "effect"
278 *
279 * Equivalence.BigInt(1n, 1n) // => true
280 * Equivalence.BigInt(1n, 2n) // => false
281 * ```
282 *
283 * @category instances
284 * @since 4.0.0
285 */
286export const BigInt: Equivalence<bigint> = isStrictEquivalent
287
288/**
289 * Combines two equivalence relations using logical AND.
290 *
291 * **When to use**
292 *
293 * Use when you need to combine exactly two equivalences with AND semantics.
294 *
295 * **Details**
296 *
297 * Returns `true` only if both equivalences return `true`. The comparison
298 * short-circuits when the first equivalence returns `false`. The result is also
299 * an equivalence that satisfies reflexive, symmetric, and transitive
300 * properties.
301 *
302 * **Example** (Combining name and age equivalences)
303 *
304 * ```ts import.meta.vitest
305 * import { Equivalence } from "effect"
306 *
307 * interface Person {
308 * name: string
309 * age: number
310 * }
311 *
312 * const nameEquivalence = Equivalence.mapInput(
313 * Equivalence.strictEqual<string>(),
314 * (p: Person) => p.name
315 * )
316 *
317 * const ageEquivalence = Equivalence.mapInput(
318 * Equivalence.strictEqual<number>(),
319 * (p: Person) => p.age
320 * )
321 *
322 * const personEquivalence = Equivalence.combine(nameEquivalence, ageEquivalence)
323 *
324 * const person1 = { name: "Alice", age: 30 }
325 * const person2 = { name: "Alice", age: 30 }
326 * const person3 = { name: "Alice", age: 31 }
327 *
328 * personEquivalence(person1, person2) // => true
329 * personEquivalence(person1, person3) // => false
330 * ```
331 *
332 * @see {@link combineAll}
333 * @see {@link mapInput}
334 * @category combining
335 * @since 2.0.0
336 */
337export const combine: {
338 <A>(that: Equivalence<A>): (self: Equivalence<A>) => Equivalence<A>
339 <A>(self: Equivalence<A>, that: Equivalence<A>): Equivalence<A>
340} = dual(2, <A>(self: Equivalence<A>, that: Equivalence<A>): Equivalence<A> => make((x, y) => self(x, y) && that(x, y)))
341
342/**
343 * Combines multiple equivalence relations into a single equivalence using logical AND.
344 *
345 * **When to use**
346 *
347 * Use when you need to combine many `Equivalence` instances from an iterable.
348 *
349 * **Details**
350 *
351 * Returns `true` only if all equivalences in the collection return `true`. The
352 * comparison stops at the first equivalence that returns `false`. Empty
353 * collections return an equivalence that always returns `true`. The result is
354 * also an equivalence that satisfies reflexive, symmetric, and transitive
355 * properties.
356 *
357 * **Example** (Combining multiple field equivalences)
358 *
359 * ```ts import.meta.vitest
360 * import { Equivalence } from "effect"
361 *
362 * interface Point3D {
363 * x: number
364 * y: number
365 * z: number
366 * }
367 *
368 * const xEq = Equivalence.mapInput(
369 * Equivalence.strictEqual<number>(),
370 * (p: Point3D) => p.x
371 * )
372 * const yEq = Equivalence.mapInput(
373 * Equivalence.strictEqual<number>(),
374 * (p: Point3D) => p.y
375 * )
376 * const zEq = Equivalence.mapInput(
377 * Equivalence.strictEqual<number>(),
378 * (p: Point3D) => p.z
379 * )
380 *
381 * const point3DEq = Equivalence.combineAll([xEq, yEq, zEq])
382 *
383 * const point1 = { x: 1, y: 2, z: 3 }
384 * const point2 = { x: 1, y: 2, z: 3 }
385 * const point3 = { x: 1, y: 2, z: 4 }
386 *
387 * point3DEq(point1, point2) // => true
388 * point3DEq(point1, point3) // => false
389 * ```
390 *
391 * **Example** (Handling empty collections)
392 *
393 * ```ts import.meta.vitest
394 * import { Equivalence } from "effect"
395 *
396 * // Empty collection always returns true
397 * const alwaysEq = Equivalence.combineAll([])
398 * alwaysEq("anything", "else") // => true
399 * ```
400 *
401 * @see {@link combine}
402 * @see {@link mapInput}
403 * @category combining
404 * @since 2.0.0
405 */
406export const combineAll = <A>(collection: Iterable<Equivalence<A>>): Equivalence<A> =>
407 make((x, y) => {
408 for (const equivalence of collection) {
409 if (!equivalence(x, y)) {
410 return false
411 }
412 }
413 return true
414 })
415
416/**
417 * Transforms an equivalence relation by mapping the input values before comparison.
418 *
419 * **When to use**
420 *
421 * Use when you need an equivalence for one type by comparing a derived value.
422 *
423 * **Details**
424 *
425 * - Applies the transformation function to both values before comparing
426 * - The transformation function should be pure and have no side effects
427 * - The resulting equivalence compares the transformed values using the provided equivalence
428 * - The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties
429 * - Useful for comparing by one property or normalizing values before
430 * comparison, such as case-insensitive strings
431 *
432 * **Example** (Deriving equivalence from an object property)
433 *
434 * ```ts import.meta.vitest
435 * import { Equivalence } from "effect"
436 *
437 * interface User {
438 * id: number
439 * name: string
440 * email: string
441 * }
442 *
443 * // Create equivalence based on user ID only
444 * const userByIdEq = Equivalence.mapInput(
445 * Equivalence.strictEqual<number>(),
446 * (user: User) => user.id
447 * )
448 *
449 * const user1 = { id: 1, name: "Alice", email: "alice@example.com" }
450 * const user2 = { id: 1, name: "Alice Smith", email: "alice.smith@example.com" }
451 * const user3 = { id: 2, name: "Bob", email: "bob@example.com" }
452 *
453 * userByIdEq(user1, user2) // => true
454 * userByIdEq(user1, user3) // => false
455 * ```
456 *
457 * **Example** (Case-insensitive string equivalence)
458 *
459 * ```ts import.meta.vitest
460 * import { Equivalence } from "effect"
461 *
462 * const caseInsensitiveEq = Equivalence.mapInput(
463 * Equivalence.strictEqual<string>(),
464 * (s: string) => s.toLowerCase()
465 * )
466 *
467 * caseInsensitiveEq("Hello", "HELLO") // => true
468 * caseInsensitiveEq("Hello", "World") // => false
469 * ```
470 *
471 * @see {@link combine}
472 * @see {@link Struct}
473 * @category mapping
474 * @since 2.0.0
475 */
476export const mapInput: {
477 <B, A>(f: (b: B) => A): (self: Equivalence<A>) => Equivalence<B>
478 <A, B>(self: Equivalence<A>, f: (b: B) => A): Equivalence<B>
479} = dual(
480 2,
481 <A, B>(self: Equivalence<A>, f: (b: B) => A): Equivalence<B> => make((x, y) => self(f(x), f(y)))
482)
483
484/**
485 * Creates an equivalence for tuples with heterogeneous element types.
486 *
487 * **When to use**
488 *
489 * Use when you need an `Equivalence` for fixed-length tuples with per-position
490 * equivalences.
491 *
492 * **Details**
493 *
494 * Tuples must have the same length; different lengths are never equivalent.
495 * Each equivalence is applied to the corresponding element position. The result
496 * returns `true` only if all elements are equivalent according to their
497 * respective equivalences, and it also satisfies reflexive, symmetric, and
498 * transitive properties.
499 *
500 * **Example** (Comparing homogeneous tuples)
501 *
502 * ```ts import.meta.vitest
503 * import { Equivalence } from "effect"
504 *
505 * const stringTupleEq = Equivalence.Tuple([
506 * Equivalence.strictEqual<string>(),
507 * Equivalence.strictEqual<string>(),
508 * Equivalence.strictEqual<string>()
509 * ])
510 *
511 * const tuple1 = ["hello", "world", "test"] as const
512 * const tuple2 = ["hello", "world", "test"] as const
513 * const tuple3 = ["hello", "world", "different"] as const
514 *
515 * stringTupleEq(tuple1, tuple2) // => true
516 * stringTupleEq(tuple1, tuple3) // => false
517 * ```
518 *
519 * **Example** (Comparing tuples with custom equivalences)
520 *
521 * ```ts import.meta.vitest
522 * import { Equivalence } from "effect"
523 *
524 * const caseInsensitive = Equivalence.mapInput(
525 * Equivalence.strictEqual<string>(),
526 * (s: string) => s.toLowerCase()
527 * )
528 *
529 * const customTupleEq = Equivalence.Tuple([
530 * caseInsensitive,
531 * caseInsensitive,
532 * caseInsensitive
533 * ])
534 *
535 * customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"]) // => true
536 * ```
537 *
538 * @category combinators
539 * @since 4.0.0
540 */
541export function Tuple<const Elements extends ReadonlyArray<Equivalence<any>>>(
542 elements: Elements
543): Equivalence<{ readonly [I in keyof Elements]: [Elements[I]] extends [Equivalence<infer A>] ? A : never }> {
544 return make((self, that) => {
545 if (self.length !== that.length) {
546 return false
547 }
548 for (let i = 0; i < self.length; i++) {
549 if (!elements[i](self[i], that[i])) {
550 return false
551 }
552 }
553 return true
554 })
555}
556
557/**
558 * @since 4.0.0
559 */
560function Array_<A>(item: Equivalence<A>): Equivalence<ReadonlyArray<A>> {
561 return make((self, that) => {
562 if (self.length !== that.length) return false
563
564 for (let i = 0; i < self.length; i++) {
565 if (!item(self[i], that[i])) return false
566 }
567
568 return true
569 })
570}
571export {
572 /**
573 * Creates an equivalence for arrays where all elements are compared using the same equivalence.
574 *
575 * **When to use**
576 *
577 * Use when you need to compare arrays with one equivalence for every element.
578 *
579 * **Details**
580 *
581 * - Requires arrays to have the same length; different lengths are never equivalent
582 * - Compares elements positionally, such as index `0` with index `0`
583 * - Returns `true` only if all corresponding elements are equivalent
584 * - Empty arrays are considered equivalent
585 * - The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties
586 *
587 * **Example** (Comparing number arrays)
588 *
589 * ```ts import.meta.vitest
590 * import { Equivalence } from "effect"
591 *
592 * const numberArrayEq = Equivalence.Array(Equivalence.strictEqual<number>())
593 *
594 * numberArrayEq([1, 2, 3], [1, 2, 3]) // => true
595 * numberArrayEq([1, 2, 3], [1, 2, 4]) // => false
596 * numberArrayEq([1, 2], [1, 2, 3]) // => false
597 * ```
598 *
599 * **Example** (Case-insensitive string array)
600 *
601 * ```ts import.meta.vitest
602 * import { Equivalence } from "effect"
603 *
604 * const caseInsensitive = Equivalence.mapInput(
605 * Equivalence.strictEqual<string>(),
606 * (s: string) => s.toLowerCase()
607 * )
608 * const stringArrayEq = Equivalence.Array(caseInsensitive)
609 *
610 * stringArrayEq(["Hello", "World"], ["HELLO", "WORLD"]) // => true
611 * stringArrayEq(["Hello"], ["Hi"]) // => false
612 * stringArrayEq([], []) // => true
613 * ```
614 *
615 * @see {@link Tuple}
616 * @see {@link Record}
617 * @category combinators
618 * @since 4.0.0
619 */
620 Array_ as Array
621}
622
623/**
624 * Creates an equivalence for objects by comparing their properties using provided equivalences.
625 *
626 * **When to use**
627 *
628 * Use when you need an `Equivalence` for objects with known, fixed property
629 * names.
630 *
631 * **Details**
632 *
633 * Compares only the properties specified in the struct definition; other
634 * properties are ignored. String and symbol keys are supported via
635 * `Reflect.ownKeys`. The result returns `true` only if all specified properties
636 * are equivalent according to their equivalences, and it also satisfies
637 * reflexive, symmetric, and transitive properties.
638 *
639 * **Example** (Comparing structs with different equivalences per field)
640 *
641 * ```ts import.meta.vitest
642 * import { Equivalence } from "effect"
643 *
644 * interface Person {
645 * name: string
646 * age: number
647 * email: string
648 * }
649 *
650 * const caseInsensitive = Equivalence.mapInput(
651 * Equivalence.strictEqual<string>(),
652 * (s: string) => s.toLowerCase()
653 * )
654 *
655 * const personEq = Equivalence.Struct({
656 * name: caseInsensitive,
657 * age: Equivalence.strictEqual<number>(),
658 * email: caseInsensitive
659 * })
660 *
661 * const person1 = { name: "Alice", age: 30, email: "alice@example.com" }
662 * const person2 = { name: "ALICE", age: 30, email: "ALICE@EXAMPLE.COM" }
663 * const person3 = { name: "Alice", age: 31, email: "alice@example.com" }
664 *
665 * personEq(person1, person2) // => true
666 * personEq(person1, person3) // => false
667 * ```
668 *
669 * **Example** (Comparing specific fields)
670 *
671 * ```ts import.meta.vitest
672 * import { Equivalence } from "effect"
673 *
674 * const nameAgeEq = Equivalence.Struct({
675 * name: Equivalence.strictEqual<string>(),
676 * age: Equivalence.strictEqual<number>()
677 * })
678 *
679 * // Only compares name and age, ignores other properties
680 * const obj1 = { name: "Alice", age: 30, extra: "ignored" }
681 * const obj2 = { name: "Alice", age: 30, extra: "different" }
682 * nameAgeEq(obj1, obj2) // => true
683 * ```
684 *
685 * @see {@link Record}
686 * @see {@link mapInput}
687 * @see {@link combine}
688 * @category combinators
689 * @since 4.0.0
690 */
691export function Struct<R extends Record<string, Equivalence<any>>>(
692 fields: R
693): Equivalence<{ readonly [K in keyof R]: [R[K]] extends [Equivalence<infer A>] ? A : never }> {
694 const keys: Array<any> = Reflect.ownKeys(fields)
695 return make((self, that) => {
696 for (const key of keys) {
697 if (!fields[key](self[key], that[key])) return false
698 }
699 return true
700 })
701}
702
703/**
704 * Creates an equivalence for objects by comparing all properties using the same equivalence.
705 *
706 * **When to use**
707 *
708 * Use when you need to compare records with the same equivalence for every
709 * property value.
710 *
711 * **Details**
712 *
713 * - Compares all properties present in both objects
714 * - Requires both objects to have the same set of keys; different keys result in `false`
715 * - All property values must be equivalent according to the provided equivalence
716 * - Supports both string and symbol keys via `Reflect.ownKeys`
717 * - Empty objects are considered equivalent
718 * - The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties
719 *
720 * **Example** (Defining records with string values)
721 *
722 * ```ts import.meta.vitest
723 * import { Equivalence } from "effect"
724 *
725 * const stringRecordEq = Equivalence.Record(Equivalence.strictEqual<string>())
726 *
727 * const record1 = { a: "hello", b: "world" }
728 * const record2 = { a: "hello", b: "world" }
729 * const record3 = { a: "hello", b: "different" }
730 * const record4 = { a: "hello" } // missing key 'b'
731 *
732 * stringRecordEq(record1, record2) // => true
733 * stringRecordEq(record1, record3) // => false
734 * stringRecordEq(record1, record4) // => false
735 * ```
736 *
737 * **Example** (Defining records with number values)
738 *
739 * ```ts import.meta.vitest
740 * import { Equivalence } from "effect"
741 *
742 * const numberRecordEq = Equivalence.Record(Equivalence.strictEqual<number>())
743 *
744 * const scores1 = { alice: 100, bob: 85 }
745 * const scores2 = { alice: 100, bob: 85 }
746 * const scores3 = { alice: 100, bob: 90 }
747 *
748 * numberRecordEq(scores1, scores2) // => true
749 * numberRecordEq(scores1, scores3) // => false
750 * ```
751 *
752 * @category combinators
753 * @since 4.0.0
754 */
755export function Record<A>(value: Equivalence<A>): Equivalence<Record<PropertyKey, A>> {
756 return make((self, that) => {
757 const selfKeys = Reflect.ownKeys(self)
758 const thatKeys = Reflect.ownKeys(that)
759
760 if (selfKeys.length !== thatKeys.length) return false
761
762 for (const key of selfKeys) {
763 if (!Object.hasOwn(that, key) || !value(self[key], that[key])) {
764 return false
765 }
766 }
767
768 return true
769 })
770}
771
772/**
773 * Creates a `Reducer` for combining `Equivalence` instances, useful for aggregating equivalences in collections.
774 *
775 * **When to use**
776 *
777 * Use when you need a reducer that combines equivalences.
778 *
779 * **Details**
780 *
781 * Returns a reducer that combines equivalences using `combine`. The identity
782 * element for empty collections is an equivalence that always returns `true`.
783 * The reducer uses `combineAll` for collections of equivalences and can be used
784 * with fold operations.
785 *
786 * **Example** (Creating a Reducer)
787 *
788 * ```ts import.meta.vitest
789 * import { Equivalence } from "effect"
790 *
791 * const reducer = Equivalence.makeReducer<number>()
792 * const equivalences = [
793 * Equivalence.strictEqual<number>(),
794 * Equivalence.make<number>((a, b) => Math.abs(a - b) < 1)
795 * ]
796 *
797 * const combined = reducer.combineAll(equivalences)
798 * // Combined equivalence requires both conditions to be true
799 * combined(1, 1) // => true
800 * combined(1, 1.5) // => false
801 * ```
802 *
803 * @see {@link combine} Combine two equivalences
804 * @see {@link combineAll} Combine multiple equivalences
805 * @see {@link Reducer} Reducer type for collection operations
806 * @category constructors
807 * @since 4.0.0
808 */
809export function makeReducer<A>() {
810 return Reducer.make<Equivalence<A>>(
811 combine,
812 () => true,
813 combineAll
814 )
815}
816
817/**
818 * Equivalence instance for `Date` objects that compares their `getTime()` values using `Equivalence.Number`.
819 *
820 * **When to use**
821 *
822 * Use when you need an `Equivalence` for JavaScript date objects by their
823 * millisecond timestamp.
824 *
825 * **Details**
826 *
827 * Different `Date` instances that represent the same millisecond timestamp are equivalent. Because `Equivalence.Number`
828 * treats `NaN` as equal to `NaN`, two invalid `Date` values are also considered equivalent.
829 *
830 * **Example** (Comparing Date values)
831 *
832 * ```ts import.meta.vitest
833 * import { Equivalence } from "effect"
834 *
835 * const d1 = new Date("2020-01-01T00:00:00.000Z")
836 * const d2 = new Date("2020-01-01T00:00:00.000Z")
837 * const d3 = new Date("2021-01-01T00:00:00.000Z")
838 * const invalidDate1 = new Date("foo")
839 * const invalidDate2 = new Date("bar")
840 *
841 * Equivalence.Date(d1, d2) // => true
842 * Equivalence.Date(d1, d3) // => false
843 * Equivalence.Date(invalidDate1, invalidDate2) // => true
844 * Equivalence.Date(invalidDate1, d1) // => false
845 * ```
846 *
847 * **Example** (Comparing reference and value equality)
848 *
849 * ```ts import.meta.vitest
850 * import { Equivalence } from "effect"
851 *
852 * const d1 = new Date(0)
853 * const d2 = new Date(0)
854 *
855 * d1 === d2 // => false
856 * Equivalence.Date(d1, d2) // => true
857 * ```
858 *
859 * @see {@link Number} for the numeric equivalence applied to each `Date#getTime()` result
860 * @see {@link mapInput} for deriving an equivalence by mapping inputs before comparison
861 * @see {@link strictEqual} for reference equality when two values must be the same object
862 * @category instances
863 * @since 2.0.0
864 */
865export const Date: Equivalence<Date> = mapInput(
866 Number,
867 (d: Date) => d.getTime()
868)