| 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 | */ |
| 10 | import type { LazyArg } from "./Function.ts" |
| 11 | import { dual } from "./Function.ts" |
| 12 | import * 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 | */ |
| 53 | export 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 | */ |
| 95 | export 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 | */ |
| 123 | export 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 | */ |
| 169 | export 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 | ) |