Traits & Behaviours
JavaScript’s built-in operators answer surprisingly few questions about your
data. === compares objects by reference, not by content. < only works for a
handful of primitives. There is no standard way to ask “do these two values mean
the same thing?” or “which of these comes first?” for your own types.
Effect closes that gap with a small set of traits — interfaces and modules that describe behaviours a value can have. The most important ones are:
Equal/Hash— value-based equality. Two values are equal when their contents match, not when they share a reference.Hashis the companion that lets hash-based collections (HashMap,HashSet) bucket values quickly.Order— a total ordering. AnOrder<A>is a comparison function that powers sorting,min/max, clamping, and range checks.Equivalence— a relaxed notion of equality. AnEquivalence<A>decides when two values should be treated as equivalent for a particular purpose (case-insensitive strings, dates by timestamp, records by a single field).
These traits are the foundation a lot of the rest of Effect is built on. When
you compare two Options, deduplicate an Array, or store domain objects in a
HashSet, you are relying on Equal, Order, and Equivalence under the hood.
A first taste
Section titled “A first taste”import { Equal, Order, Array } from "effect"
// Value equality: same contents, different referencesconsole.log(Equal.equals({ id: 1 }, { id: 1 })) // trueconsole.log({ id: 1 } === { id: 1 }) // false (reference equality)
// A reusable comparator for objects, derived from a primitive Orderconst byAge = Order.mapInput( Order.Number, (user: { name: string; age: number }) => user.age)
const users = [ { name: "Charlie", age: 30 }, { name: "Bob", age: 25 }]
console.log(Array.sort(users, byAge).map((u) => u.name))// ["Bob", "Charlie"]Equal.equals walks the structure of both values and compares them field by
field. Order.mapInput adapts the built-in Order.Number so it can compare
whole user objects by their age. Neither requires you to write any
comparison logic by hand — you compose existing behaviours instead.
How to think about traits
Section titled “How to think about traits”There are two ways a type participates in these behaviours:
- The type implements the interface. Classes can implement
EqualandHashdirectly so thatEqual.equalsand hash collections understand them. This is howDataclasses get structural equality for free. - You supply an instance separately.
Order<A>andEquivalence<A>are plain functions you build and pass to APIs likeArray.sortorArray.dedupeWith. The value itself does not need to know anything about them.
Equal and Equivalence look similar but serve different roles. Equal is the
one canonical structural equality used throughout Effect; Equivalence lets
you define many situational notions of “the same” for a single type.
In this section
Section titled “In this section”- Equal & Hash — value equality with
Equal, theHashcontract, and how to implement both on your own classes. - Order — building total orders, composing them for multi-field sorting, and turning them into predicates and range checks.
- Equivalence — defining custom equality relations and combining them for structs, tuples, arrays, and records.
See also
Section titled “See also”- Data Types —
Dataclasses implementEqual/Hashautomatically. - Schema — derive an
Equivalencefrom a schema withSchema.toEquivalence.