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")) // -1The Ordering type
Section titled “The Ordering type”The value an Order returns — -1, 0, or 1 — has its own module,
Ordering. It is just a normalized comparison result, but the module gives you
helpers to interpret one, flip it, and combine several of them in priority
order. Combining is what powers multi-key sorting under the hood.
import { Order, Ordering } from "effect"
// An Ordering is one of exactly three numeric literalsconst result: Ordering.Ordering = Order.Number(1, 2)console.log(result) // => -1Ordering
Section titled “Ordering”The result of comparing two values: -1 (less than), 0 (equal), or 1
(greater than). This is the return type of every Order<A>.
import { Ordering } from "effect"
// A hand-written comparator must return one of these three literalsconst compare = (a: number, b: number): Ordering.Ordering => a < b ? -1 : a > b ? 1 : 0
console.log(compare(5, 10)) // => -1console.log(compare(10, 5)) // => 1console.log(compare(5, 5)) // => 0reverse
Section titled “reverse”Flips an ordering: -1 becomes 1 and 1 becomes -1, while 0 is left
unchanged. Useful for turning an ascending result into a descending one.
import { Ordering } from "effect"
console.log(Ordering.reverse(-1)) // => 1console.log(Ordering.reverse(1)) // => -1console.log(Ordering.reverse(0)) // => 0Branches on the three possible outcomes in a single expression, calling the
matching thunk: onLessThan, onEqual, or onGreaterThan. Both data-first and
data-last (pipeable) signatures are available.
import { Ordering } from "effect"
const describe = Ordering.match({ onLessThan: () => "before", onEqual: () => "same", onGreaterThan: () => "after"})
console.log(describe(-1)) // => "before"console.log(describe(0)) // => "same"console.log(describe(1)) // => "after"
// data-first formconsole.log( Ordering.match(1, { onLessThan: () => "before", onEqual: () => "same", onGreaterThan: () => "after" })) // => "after"Reducer
Section titled “Reducer”A Reducer for Orderings whose combine keeps the
first non-zero result and treats 0 as the identity. This is the exact rule
behind tie-breaking: compare by a primary criterion, and only consult the next
when the previous one returned 0. Its combineAll short-circuits as soon as it
finds a non-zero ordering.
import { Ordering } from "effect"
// combine: first non-zero wins, 0 is neutralconsole.log(Ordering.Reducer.combine(0, -1)) // => -1console.log(Ordering.Reducer.combine(1, -1)) // => 1 (first non-zero wins)console.log(Ordering.Reducer.combine(0, 0)) // => 0console.log(Ordering.Reducer.initialValue) // => 0 (the identity)
// combineAll folds a list of comparison results, primary criterion firstconsole.log(Ordering.Reducer.combineAll([0, 0, 1, -1])) // => 1console.log(Ordering.Reducer.combineAll([])) // => 0This is the same semantics Order.combine and Order.combineAll apply when
chaining orders. Sorting users by age, then breaking ties by name, reduces to
combining each field’s Ordering with Ordering.Reducer:
import { Order, Ordering } from "effect"
interface User { readonly name: string readonly age: number}
const a: User = { name: "Alice", age: 30 }const b: User = { name: "Bob", age: 30 }
// Equivalent to Order.combine(byAge, byName)(a, b)const byAgeThenName = Ordering.Reducer.combineAll([ Order.Number(a.age, b.age), // => 0 (ages tie) Order.String(a.name, b.name) // => -1 ("Alice" < "Bob")])
console.log(byAgeThenName) // => -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.