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 */
12import 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 */
41export 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 */
79export 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 */
110export 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 */
143export 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 */
176export 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 */
205export 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 */
234export 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 */
266export 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 */
300export function intercalate<A>(middle: A) {
301 return (combiner: Combiner<A>): Combiner<A> =>
302 make((self, that) => combiner.combine(self, combiner.combine(middle, that)))
303}