# 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.

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

// Built-in orders for primitives
console.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

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.

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

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

// Reuse Order.Number, but compare Users by their age
const 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

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.

```ts
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 name
const 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:

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

const byAgeThenName = Order.Struct({
  age: Order.Number, // highest-priority field first
  name: Order.String
})
```
**Note:** Field order matters in `Order.Struct` and in `Order.combine`: the first
comparison that returns a non-zero result wins, so put the highest-priority
criterion first.

## Reversing and choosing direction

`flip` reverses an order, turning ascending into descending without touching the
original:

```ts
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

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:

```ts
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 equal
console.log(Order.min(Order.Number)(3, 7)) // 3
console.log(Order.max(Order.Number)(3, 7)) // 7

// Clamp a value into a range and test membership
const clamp = Order.clamp(Order.Number)({ minimum: 1, maximum: 5 })
console.log(clamp(0)) // 1
console.log(clamp(9)) // 5

const inRange = Order.isBetween(Order.Number)
console.log(inRange(3, { minimum: 1, maximum: 5 })) // true
```

There are matching predicates for every direction: `isLessThan`,
`isLessThanOrEqualTo`, `isGreaterThan`, and `isGreaterThanOrEqualTo`.

## 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.

```ts
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

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.

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

// Order strings by length, longest first
const 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")) // -1
```
**Caution:** `Order.make` short-circuits to `0` when the two arguments are reference-equal
(`self === that`), so your comparison function is never called for identical
references. Keep that in mind if your logic depends on distinguishing the same
object from itself — it cannot.

## 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.

```ts
import { Order, Ordering } from "effect"

// An Ordering is one of exactly three numeric literals
const result: Ordering.Ordering = Order.Number(1, 2)
console.log(result) // => -1
```
**Note:** Only normalized values (`-1`, `0`, `1`) are valid `Ordering`s. Raw comparator
results like `a.localeCompare(b)` may return other integers, so do not cast them
to `Ordering` without normalizing first.

### 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>`.

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

// A hand-written comparator must return one of these three literals
const compare = (a: number, b: number): Ordering.Ordering =>
  a < b ? -1 : a > b ? 1 : 0

console.log(compare(5, 10)) // => -1
console.log(compare(10, 5)) // => 1
console.log(compare(5, 5)) // => 0
```

### 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.

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

console.log(Ordering.reverse(-1)) // => 1
console.log(Ordering.reverse(1)) // => -1
console.log(Ordering.reverse(0)) // => 0
```

### match

Branches 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.

```ts
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 form
console.log(
  Ordering.match(1, {
    onLessThan: () => "before",
    onEqual: () => "same",
    onGreaterThan: () => "after"
  })
) // => "after"
```

### Reducer

A `Reducer` for `Ordering`s 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.

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

// combine: first non-zero wins, 0 is neutral
console.log(Ordering.Reducer.combine(0, -1)) // => -1
console.log(Ordering.Reducer.combine(1, -1)) // => 1  (first non-zero wins)
console.log(Ordering.Reducer.combine(0, 0)) // => 0
console.log(Ordering.Reducer.initialValue) // => 0  (the identity)

// combineAll folds a list of comparison results, primary criterion first
console.log(Ordering.Reducer.combineAll([0, 0, 1, -1])) // => 1
console.log(Ordering.Reducer.combineAll([])) // => 0
```

This 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`:

```ts
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) // => -1
```

## See also

- [Equivalence](https://effect.plants.sh/traits/equivalence/) — when you need equality but not a full
  less-than/greater-than ordering.
- [Data Types](https://effect.plants.sh/data-types/) — collections and values you will frequently sort.
- [Schema](https://effect.plants.sh/schema/) — order-based refinements (`greaterThan`, `between`) build
  on `Order` instances.