| 1 | /** |
| 2 | * Defines reusable rules for merging two values of the same type. |
| 3 | * |
| 4 | * A `Combiner<A>` contains one operation, `combine(self, that)`, which returns |
| 5 | * the merged value. It does not define an initial value for reducing a |
| 6 | * collection; use a `Reducer` when you need that. This module includes the |
| 7 | * `Combiner` interface, a constructor for custom combining logic, and common |
| 8 | * combiners for choosing or ordering values. |
| 9 | * |
| 10 | * @since 4.0.0 |
| 11 | */ |
| 12 | import type * as Order from "./Order.ts" |
| 13 | |
| 14 | /** |
| 15 | * Represents a strategy for combining two values of the same type `A`. A |
| 16 | * `Combiner` contains a single `combine` method that takes two values and |
| 17 | * returns a merged result. It does not include an identity/empty value; use |
| 18 | * `Reducer` when you need one. |
| 19 | * |
| 20 | * **When to use** |
| 21 | * |
| 22 | * Use when you need to describe how two values of the same type |
| 23 | * merge, pass a reusable combining strategy to library functions like |
| 24 | * `Struct.makeCombiner` or `Option.makeCombinerFailFast`, or define the |
| 25 | * combining step for a `Reducer`. |
| 26 | * |
| 27 | * **Example** (Combining numbers with addition) |
| 28 | * |
| 29 | * ```ts import.meta.vitest |
| 30 | * import { Combiner } from "effect" |
| 31 | * |
| 32 | * const Sum = Combiner.make<number>((self, that) => self + that) |
| 33 | * |
| 34 | * Sum.combine(3, 4) // => 7 |
| 35 | * ``` |
| 36 | * |
| 37 | * @see {@link make} – create a `Combiner` from a function |
| 38 | * @category models |
| 39 | * @since 4.0.0 |
| 40 | */ |
| 41 | export interface Combiner<A> { |
| 42 | /** |
| 43 | * Combines two values into a new value. |
| 44 | * |
| 45 | * **When to use** |
| 46 | * |
| 47 | * Use to merge two values according to this combining strategy. |
| 48 | */ |
| 49 | readonly combine: (self: A, that: A) => A |
| 50 | } |
| 51 | |
| 52 | /** |
| 53 | * Creates a `Combiner` from a binary function. |
| 54 | * |
| 55 | * **When to use** |
| 56 | * |
| 57 | * Use when you have a custom combining operation that is not covered by |
| 58 | * the built-in constructors (`min`, `max`, `first`, `last`, `constant`). |
| 59 | * |
| 60 | * **Details** |
| 61 | * |
| 62 | * The returned combiner's `combine` method delegates to the provided function. |
| 63 | * Any purity, associativity, or mutation behavior comes from that function. |
| 64 | * |
| 65 | * **Example** (Multiplying numbers) |
| 66 | * |
| 67 | * ```ts import.meta.vitest |
| 68 | * import { Combiner } from "effect" |
| 69 | * |
| 70 | * const Product = Combiner.make<number>((self, that) => self * that) |
| 71 | * |
| 72 | * Product.combine(3, 5) // => 15 |
| 73 | * ``` |
| 74 | * |
| 75 | * @see {@link Combiner} – the interface this creates |
| 76 | * @category constructors |
| 77 | * @since 4.0.0 |
| 78 | */ |
| 79 | export function make<A>(combine: (self: A, that: A) => A): Combiner<A> { |
| 80 | return { combine } |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Reverses the argument order of a combiner's `combine` method. |
| 85 | * |
| 86 | * **When to use** |
| 87 | * |
| 88 | * Use when you want the right-hand value to act as the accumulator, or need to |
| 89 | * reverse a non-commutative combiner such as string concatenation. |
| 90 | * |
| 91 | * **Details** |
| 92 | * |
| 93 | * Returns a new `Combiner` where `combine(self, that)` calls the original |
| 94 | * combiner as `combine(that, self)`. |
| 95 | * |
| 96 | * **Example** (Reversing string concatenation) |
| 97 | * |
| 98 | * ```ts import.meta.vitest |
| 99 | * import { Combiner, String } from "effect" |
| 100 | * |
| 101 | * const Prepend = Combiner.flip(String.ReducerConcat) |
| 102 | * |
| 103 | * Prepend.combine("a", "b") // => "ba" |
| 104 | * ``` |
| 105 | * |
| 106 | * @see {@link make} |
| 107 | * @category combinators |
| 108 | * @since 4.0.0 |
| 109 | */ |
| 110 | export function flip<A>(combiner: Combiner<A>): Combiner<A> { |
| 111 | return make((self, that) => combiner.combine(that, self)) |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Creates a `Combiner` that returns the smaller of two values according to |
| 116 | * the provided `Order`. |
| 117 | * |
| 118 | * **When to use** |
| 119 | * |
| 120 | * Use when you want to accumulate the minimum value across a collection or |
| 121 | * build a `Reducer` that tracks the running minimum. |
| 122 | * |
| 123 | * **Details** |
| 124 | * |
| 125 | * The combiner compares values using the given `Order`. When values are equal, |
| 126 | * it returns `that` (the second argument). |
| 127 | * |
| 128 | * **Example** (Selecting the minimum of two numbers) |
| 129 | * |
| 130 | * ```ts import.meta.vitest |
| 131 | * import { Combiner, Number } from "effect" |
| 132 | * |
| 133 | * const Min = Combiner.min(Number.Order) |
| 134 | * |
| 135 | * Min.combine(3, 1) // => 1 |
| 136 | * Min.combine(1, 3) // => 1 |
| 137 | * ``` |
| 138 | * |
| 139 | * @see {@link max} |
| 140 | * @category constructors |
| 141 | * @since 4.0.0 |
| 142 | */ |
| 143 | export function min<A>(order: Order.Order<A>): Combiner<A> { |
| 144 | return make((self, that) => order(self, that) === -1 ? self : that) |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Creates a `Combiner` that returns the larger of two values according to |
| 149 | * the provided `Order`. |
| 150 | * |
| 151 | * **When to use** |
| 152 | * |
| 153 | * Use when you want to accumulate the maximum value across a collection or |
| 154 | * build a `Reducer` that tracks the running maximum. |
| 155 | * |
| 156 | * **Details** |
| 157 | * |
| 158 | * The combiner compares values using the given `Order`. When values are equal, |
| 159 | * it returns `that` (the second argument). |
| 160 | * |
| 161 | * **Example** (Selecting the maximum of two numbers) |
| 162 | * |
| 163 | * ```ts import.meta.vitest |
| 164 | * import { Combiner, Number } from "effect" |
| 165 | * |
| 166 | * const Max = Combiner.max(Number.Order) |
| 167 | * |
| 168 | * Max.combine(3, 1) // => 3 |
| 169 | * Max.combine(1, 3) // => 3 |
| 170 | * ``` |
| 171 | * |
| 172 | * @see {@link min} |
| 173 | * @category constructors |
| 174 | * @since 4.0.0 |
| 175 | */ |
| 176 | export function max<A>(order: Order.Order<A>): Combiner<A> { |
| 177 | return make((self, that) => order(self, that) === 1 ? self : that) |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Creates a `Combiner` that always returns the first (left) argument. |
| 182 | * |
| 183 | * **When to use** |
| 184 | * |
| 185 | * Use when you want "first write wins" semantics while merging values. |
| 186 | * |
| 187 | * **Details** |
| 188 | * |
| 189 | * `combine(self, that)` returns `self` and ignores `that`. |
| 190 | * |
| 191 | * **Example** (Keeping the first value) |
| 192 | * |
| 193 | * ```ts import.meta.vitest |
| 194 | * import { Combiner } from "effect" |
| 195 | * |
| 196 | * const First = Combiner.first<number>() |
| 197 | * |
| 198 | * First.combine(1, 2) // => 1 |
| 199 | * ``` |
| 200 | * |
| 201 | * @see {@link last} |
| 202 | * @category constructors |
| 203 | * @since 4.0.0 |
| 204 | */ |
| 205 | export function first<A>(): Combiner<A> { |
| 206 | return make((self, _) => self) |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Creates a `Combiner` that always returns the last (right) argument. |
| 211 | * |
| 212 | * **When to use** |
| 213 | * |
| 214 | * Use when you want "last write wins" semantics while merging values. |
| 215 | * |
| 216 | * **Details** |
| 217 | * |
| 218 | * `combine(self, that)` returns `that` and ignores `self`. |
| 219 | * |
| 220 | * **Example** (Keeping the last value) |
| 221 | * |
| 222 | * ```ts import.meta.vitest |
| 223 | * import { Combiner } from "effect" |
| 224 | * |
| 225 | * const Last = Combiner.last<number>() |
| 226 | * |
| 227 | * Last.combine(1, 2) // => 2 |
| 228 | * ``` |
| 229 | * |
| 230 | * @see {@link first} |
| 231 | * @category constructors |
| 232 | * @since 4.0.0 |
| 233 | */ |
| 234 | export function last<A>(): Combiner<A> { |
| 235 | return make((_, that) => that) |
| 236 | } |
| 237 | |
| 238 | /** |
| 239 | * Creates a `Combiner` that ignores both arguments and always returns the |
| 240 | * given constant value. |
| 241 | * |
| 242 | * **When to use** |
| 243 | * |
| 244 | * Use when you need a combiner that always returns a fixed value, including |
| 245 | * when a generic API requires a combiner but the result is predetermined. |
| 246 | * |
| 247 | * **Details** |
| 248 | * |
| 249 | * `combine(self, that)` returns the constant `a` and ignores both arguments. |
| 250 | * |
| 251 | * **Example** (Always returning zero) |
| 252 | * |
| 253 | * ```ts import.meta.vitest |
| 254 | * import { Combiner } from "effect" |
| 255 | * |
| 256 | * const Zero = Combiner.constant(0) |
| 257 | * |
| 258 | * Zero.combine(42, 99) // => 0 |
| 259 | * ``` |
| 260 | * |
| 261 | * @see {@link first} |
| 262 | * @see {@link last} |
| 263 | * @category constructors |
| 264 | * @since 4.0.0 |
| 265 | */ |
| 266 | export function constant<A>(a: A): Combiner<A> { |
| 267 | return make(() => a) |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Wraps a `Combiner` so that a separator value is inserted between every |
| 272 | * pair of combined elements. |
| 273 | * |
| 274 | * **When to use** |
| 275 | * |
| 276 | * Use when you need to inject a fixed separator between accumulated values, |
| 277 | * such as when building delimited strings, paths, or CSV-like output by |
| 278 | * repeated combination. |
| 279 | * |
| 280 | * **Details** |
| 281 | * |
| 282 | * `intercalate(middle)(combiner).combine(self, that)` is equivalent to |
| 283 | * `combiner.combine(self, combiner.combine(middle, that))`. This function is |
| 284 | * curried: first provide the separator, then the base combiner. |
| 285 | * |
| 286 | * **Example** (Joining strings with a separator) |
| 287 | * |
| 288 | * ```ts import.meta.vitest |
| 289 | * import { Combiner, String } from "effect" |
| 290 | * |
| 291 | * const commaSep = Combiner.intercalate(",")(String.ReducerConcat) |
| 292 | * |
| 293 | * commaSep.combine("a", "b") // => "a,b" |
| 294 | * ``` |
| 295 | * |
| 296 | * @see {@link make} |
| 297 | * @category combinators |
| 298 | * @since 4.0.0 |
| 299 | */ |
| 300 | export function intercalate<A>(middle: A) { |
| 301 | return (combiner: Combiner<A>): Combiner<A> => |
| 302 | make((self, that) => combiner.combine(self, combiner.combine(middle, that))) |
| 303 | } |