1/**
2 * Reusable strategies for reducing many values into one value. A `Reducer<A>`
3 * extends `Combiner.Combiner` with an `initialValue` for empty collections and
4 * a `combineAll` method for folding an entire iterable. This module provides a
5 * constructor for reducers and a helper for reversing the order in which values
6 * are combined.
7 *
8 * @since 4.0.0
9 */
10
11import type * as Combiner from "./Combiner.ts"
12
13/**
14 * Represents a strategy for reducing a collection of values of type `A` into
15 * a single result.
16 *
17 * **When to use**
18 *
19 * Use when you need to fold/reduce a collection into a single value.
20 * - You want a reusable reducing strategy that can be passed to library
21 * functions like `Struct.makeReducer`, `Option.makeReducer`, or
22 * `Record.makeReducerUnion`.
23 * - You need both the combining logic *and* a known starting value.
24 *
25 * **Details**
26 *
27 * Extends {@link Combiner.Combiner} with:
28 *
29 * - `initialValue` – the identity/neutral element for `combine`.
30 * - `combineAll` – folds an entire `Iterable<A>` from `initialValue`.
31 *
32 * Many modules ship pre-built reducers:
33 *
34 * - `Number.ReducerSum`, `Number.ReducerMultiply`
35 * - `String.ReducerConcat`
36 * - `Boolean.ReducerAnd`, `Boolean.ReducerOr`
37 *
38 * **Example** (String concatenation reducer)
39 *
40 * ```ts import.meta.vitest
41 * import { Reducer } from "effect"
42 *
43 * const Concat = Reducer.make<string>((a, b) => a + b, "")
44 *
45 * Concat.combineAll(["hello", " ", "world"]) // => "hello world"
46 * ```
47 *
48 * @see {@link make} – create a `Reducer` from a function and initial value
49 * @see {@link Combiner.Combiner} – parent interface without `initialValue`
50 *
51 * @category models
52 * @since 4.0.0
53 */
54export interface Reducer<A> extends Combiner.Combiner<A> {
55 /**
56 * Neutral starting value (combining with this changes nothing).
57 *
58 * **When to use**
59 *
60 * Use to seed a reduction and represent the result of reducing an empty collection.
61 */
62 readonly initialValue: A
63
64 /**
65 * Combines all values in the collection, starting from `initialValue`.
66 *
67 * **When to use**
68 *
69 * Use to reduce an iterable with this reducer's initial value and combining operation.
70 */
71 readonly combineAll: (collection: Iterable<A>) => A
72}
73
74/**
75 * Creates a `Reducer` from a `combine` function and an `initialValue`.
76 *
77 * **When to use**
78 *
79 * Use when you have a custom reducing operation not covered by a pre-built reducer.
80 * - You want to provide an optimized `combineAll` (e.g. short-circuiting on
81 * a known absorbing element like `0` for multiplication).
82 *
83 * **Details**
84 *
85 * - If `combineAll` is omitted, a default left-to-right fold starting from
86 * `initialValue` is used.
87 * - If `combineAll` is provided, it completely replaces the default fold.
88 *
89 * **Example** (Multiplying with short-circuit)
90 *
91 * ```ts import.meta.vitest
92 * import { Reducer } from "effect"
93 *
94 * const Product = Reducer.make<number>(
95 * (a, b) => a * b,
96 * 1,
97 * (collection) => {
98 * let acc = 1
99 * for (const n of collection) {
100 * if (n === 0) return 0
101 * acc *= n
102 * }
103 * return acc
104 * }
105 * )
106 *
107 * Product.combineAll([2, 3, 4]) // => 24
108 * Product.combineAll([2, 0, 4]) // => 0
109 * ```
110 *
111 * @see {@link Reducer} – the interface this creates
112 * @see {@link flip} – reverse the argument order
113 *
114 * @category constructors
115 * @since 4.0.0
116 */
117export function make<A>(
118 combine: (self: A, that: A) => A,
119 initialValue: A,
120 combineAll?: (collection: Iterable<A>) => A
121): Reducer<A> {
122 return {
123 combine,
124 initialValue,
125 combineAll: combineAll ??
126 ((collection) => {
127 let out = initialValue
128 for (const value of collection) {
129 out = combine(out, value)
130 }
131 return out
132 })
133 }
134}
135
136/**
137 * Reverses the argument order of a reducer's `combine` method.
138 *
139 * **When to use**
140 *
141 * Use when you want the right-hand value to act as the accumulator, or need to
142 * reverse a non-commutative reducer such as string concatenation.
143 *
144 * **Details**
145 *
146 * - Returns a new `Reducer` where `combine(self, that)` calls the original
147 * reducer as `combine(that, self)`.
148 * - The `initialValue` is preserved from the original reducer.
149 * - The `combineAll` is re-derived from the flipped `combine` (using the
150 * default left-to-right fold), not carried over from the original.
151 *
152 * **Example** (Reversing string concatenation)
153 *
154 * ```ts import.meta.vitest
155 * import { Reducer, String } from "effect"
156 *
157 * const Prepend = Reducer.flip(String.ReducerConcat)
158 *
159 * Prepend.combine("a", "b") // => "ba"
160 * Prepend.combineAll(["a", "b", "c"]) // => "cba"
161 * ```
162 *
163 * @see {@link make}
164 * @see {@link Combiner.flip} – the same operation on a plain `Combiner`
165 *
166 * @category combinators
167 * @since 4.0.0
168 */
169export function flip<A>(reducer: Reducer<A>): Reducer<A> {
170 return make((self, that) => reducer.combine(that, self), reducer.initialValue)
171}