1/**
2 * The standard result of comparing two values. An `Ordering` is `-1` when the
3 * first value is less than the second, `0` when both values compare as equal,
4 * and `1` when the first value is greater than the second. This module also
5 * provides helpers for reversing an ordering, matching on the three cases, and
6 * combining ordered comparison results with a reducer.
7 *
8 * @since 2.0.0
9 */
10import type { LazyArg } from "./Function.ts"
11import { dual } from "./Function.ts"
12import * as Reducer_ from "./Reducer.ts"
13
14/**
15 * Represents the result of comparing two values.
16 *
17 * **When to use**
18 *
19 * Use to model a normalized comparison result that is exactly less than,
20 * equal to, or greater than.
21 *
22 * **Details**
23 *
24 * - `-1` indicates the first value is less than the second
25 * - `0` indicates the values are equal
26 * - `1` indicates the first value is greater than the second
27 *
28 * **Example** (Defining comparison results)
29 *
30 * ```ts import.meta.vitest
31 * import type { Ordering } from "effect"
32 *
33 * // Custom comparison function
34 * const compareNumbers = (a: number, b: number): Ordering.Ordering => {
35 * if (a < b) return -1
36 * if (a > b) return 1
37 * return 0
38 * }
39 *
40 * compareNumbers(5, 10) // => -1
41 * compareNumbers(10, 5) // => 1
42 * compareNumbers(5, 5) // => 0
43 *
44 * // Using with string comparison
45 * const compareStrings = (a: string, b: string): Ordering.Ordering => {
46 * return a.localeCompare(b) as Ordering.Ordering
47 * }
48 * ```
49 *
50 * @category models
51 * @since 2.0.0
52 */
53export type Ordering = -1 | 0 | 1
54
55/**
56 * Reverses the ordering of the input Ordering.
57 * This is useful for creating descending sort orders from ascending ones.
58 *
59 * **When to use**
60 *
61 * Use to flip an ordering result when reversing sort direction or comparison
62 * priority.
63 *
64 * **Example** (Reversing comparison order)
65 *
66 * ```ts import.meta.vitest
67 * import { Ordering } from "effect"
68 *
69 * // Basic reversal
70 * Ordering.reverse(1) // => -1
71 * Ordering.reverse(-1) // => 1
72 * Ordering.reverse(0) // => 0
73 *
74 * // Creating descending sort from ascending comparison
75 * const compareNumbers = (a: number, b: number): Ordering.Ordering =>
76 * a < b ? -1 : a > b ? 1 : 0
77 *
78 * const compareDescending = (a: number, b: number): Ordering.Ordering =>
79 * Ordering.reverse(compareNumbers(a, b))
80 *
81 * const numbers = [3, 1, 4, 1, 5]
82 * numbers.sort(compareNumbers) // [1, 1, 3, 4, 5] (ascending)
83 * numbers.sort(compareDescending) // [5, 4, 3, 1, 1] (descending)
84 *
85 * // Useful for toggling sort direction
86 * const createSorter = (ascending: boolean) => (a: number, b: number) => {
87 * const ordering = compareNumbers(a, b)
88 * return ascending ? ordering : Ordering.reverse(ordering)
89 * }
90 * ```
91 *
92 * @category transforming
93 * @since 2.0.0
94 */
95export const reverse = (o: Ordering): Ordering => (o === -1 ? 1 : o === 1 ? -1 : 0)
96
97/**
98 * Matches an `Ordering` value and returns the branch selected by that ordering.
99 *
100 * **When to use**
101 *
102 * Use to branch on the three possible comparison outcomes in one expression.
103 *
104 * **Example** (Pattern matching on orderings)
105 *
106 * ```ts import.meta.vitest
107 * import { Function, Ordering } from "effect"
108 *
109 * const toMessage = Ordering.match({
110 * onLessThan: Function.constant("less than"),
111 * onEqual: Function.constant("equal"),
112 * onGreaterThan: Function.constant("greater than")
113 * })
114 *
115 * toMessage(-1) // => "less than"
116 * toMessage(0) // => "equal"
117 * toMessage(1) // => "greater than"
118 * ```
119 *
120 * @category pattern matching
121 * @since 2.0.0
122 */
123export const match: {
124 <A, B, C = B>(
125 options: {
126 readonly onLessThan: LazyArg<A>
127 readonly onEqual: LazyArg<B>
128 readonly onGreaterThan: LazyArg<C>
129 }
130 ): (self: Ordering) => A | B | C
131 <A, B, C = B>(
132 o: Ordering,
133 options: {
134 readonly onLessThan: LazyArg<A>
135 readonly onEqual: LazyArg<B>
136 readonly onGreaterThan: LazyArg<C>
137 }
138 ): A | B | C
139} = dual(2, <A, B, C = B>(
140 self: Ordering,
141 { onEqual, onGreaterThan, onLessThan }: {
142 readonly onLessThan: LazyArg<A>
143 readonly onEqual: LazyArg<B>
144 readonly onGreaterThan: LazyArg<C>
145 }
146): A | B | C => self === -1 ? onLessThan() : self === 0 ? onEqual() : onGreaterThan())
147
148/**
149 * Reducer for combining `Ordering`s.
150 *
151 * **When to use**
152 *
153 * Use to combine multiple comparison results in priority order, such as
154 * checking secondary criteria only when earlier criteria compare as equal.
155 *
156 * **Details**
157 *
158 * If any of the `Ordering`s is non-zero, the result is the first non-zero `Ordering`.
159 * If all the `Ordering`s are zero, the result is zero.
160 *
161 * **Gotchas**
162 *
163 * `combineAll` stops consuming the iterable as soon as it finds a non-zero
164 * `Ordering`.
165 *
166 * @category ordering
167 * @since 4.0.0
168 */
169export const Reducer: Reducer_.Reducer<Ordering> = Reducer_.make<Ordering>(
170 (self, that) => self !== 0 ? self : that,
171 0,
172 (collection) => {
173 let ordering: Ordering = 0
174 for (ordering of collection) {
175 if (ordering !== 0) {
176 return ordering
177 }
178 }
179 return ordering
180 }
181)