Order
An Order<A> is a comparison function: given two values it returns -1 (the
first comes before the second), 0 (they are equal for this ordering), or 1
(the first comes after the second). That single behaviour powers sorting,
min/max, clamping, range checks, and ordered data structures.
The module ships ready-made orders for primitives and combinators to build new ones from existing ones — so you almost never write a raw comparison by hand.
import { Order } from "effect"
// Built-in orders for primitivesconsole.log(Order.Number(1, 2)) // -1 (1 comes before 2)console.log(Order.String("b", "a")) // 1 (b comes after a)console.log(Order.Number(1, 1)) // 0 (equal)Ordering objects by a field
Section titled “Ordering objects by a field”You rarely have a bare number to sort. The key combinator is mapInput: it
adapts an existing order to a larger type by extracting the value to compare.
import { Array, Order } from "effect"
interface User { readonly name: string readonly age: number}
// Reuse Order.Number, but compare Users by their ageconst byAge = Order.mapInput(Order.Number, (user: User) => user.age)
const users: ReadonlyArray<User> = [ { name: "Charlie", age: 30 }, { name: "Bob", age: 25 }, { name: "Alice", age: 30 }]
console.log(Array.sort(users, byAge).map((u) => u.name))// ["Bob", "Charlie", "Alice"]Multi-field sorting
Section titled “Multi-field sorting”Sorting by age alone leaves the two 30-year-olds in arbitrary order. combine
chains orders: it uses the first, and only falls back to the second when the
first reports a tie. This is exactly lexicographic, “sort by A then by B”
ordering.
import { Array, Order } from "effect"
interface User { readonly name: string readonly age: number}
const byAge = Order.mapInput(Order.Number, (u: User) => u.age)const byName = Order.mapInput(Order.String, (u: User) => u.name)
// Compare by age first; break ties by nameconst byAgeThenName = Order.combine(byAge, byName)
const users: ReadonlyArray<User> = [ { name: "Charlie", age: 30 }, { name: "Bob", age: 25 }, { name: "Alice", age: 30 }]
console.log(Array.sort(users, byAgeThenName).map((u) => u.name))// ["Bob", "Alice", "Charlie"] (age ascending, then name within age 30)For a fixed struct you can express the same thing declaratively with
Order.Struct, which compares fields in the order they appear:
import { Order } from "effect"
const byAgeThenName = Order.Struct({ age: Order.Number, // highest-priority field first name: Order.String})Reversing and choosing direction
Section titled “Reversing and choosing direction”flip reverses an order, turning ascending into descending without touching the
original:
import { Array, Order } from "effect"
const byAge = Order.mapInput(Order.Number, (u: { age: number }) => u.age)const byAgeDesc = Order.flip(byAge)
const people = [{ age: 20 }, { age: 40 }, { age: 30 }]console.log(Array.sort(people, byAgeDesc).map((p) => p.age))// [40, 30, 20]From order to predicates and bounds
Section titled “From order to predicates and bounds”An Order is more than a sort key. The module turns it into boolean predicates
and selection helpers, all of which take the order as their first argument:
import { Order } from "effect"
const lt = Order.isLessThan(Order.Number)console.log(lt(1, 2)) // true
// min / max return the first argument when values compare equalconsole.log(Order.min(Order.Number)(3, 7)) // 3console.log(Order.max(Order.Number)(3, 7)) // 7
// Clamp a value into a range and test membershipconst clamp = Order.clamp(Order.Number)({ minimum: 1, maximum: 5 })console.log(clamp(0)) // 1console.log(clamp(9)) // 5
const inRange = Order.isBetween(Order.Number)console.log(inRange(3, { minimum: 1, maximum: 5 })) // trueThere are matching predicates for every direction: isLessThan,
isLessThanOrEqualTo, isGreaterThan, and isGreaterThanOrEqualTo.
Composite orders for arrays and tuples
Section titled “Composite orders for arrays and tuples”Order.Tuple compares fixed-length, heterogeneous positions; Order.Array
compares same-typed arrays element by element, with shorter arrays sorting first
when one is a prefix of the other.
import { Order } from "effect"
const byTuple = Order.Tuple([Order.Number, Order.String])console.log(byTuple([1, "b"], [1, "a"])) // 1 (numbers tie, "b" > "a")
const byArray = Order.Array(Order.Number)console.log(byArray([1, 2], [1, 2, 3])) // -1 (prefix is "less")Building an order from scratch
Section titled “Building an order from scratch”When no combinator fits, Order.make wraps a raw comparison. It must return
-1, 0, or 1, and it should satisfy the ordering laws — totality,
antisymmetry, and transitivity — or sorting and range checks may misbehave.
import { Order } from "effect"
// Order strings by length, longest firstconst byLengthDesc = Order.make<string>((a, b) => { if (a.length > b.length) return -1 if (a.length < b.length) return 1 return 0})
console.log(byLengthDesc("aaa", "b")) // -1See also
Section titled “See also”- Equivalence — when you need equality but not a full less-than/greater-than ordering.
- Data Types — collections and values you will frequently sort.
- Schema — order-based refinements (
greaterThan,between) build onOrderinstances.