• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

domix / dmx-fun / 25619921491

10 May 2026 04:36AM UTC coverage: 98.278% (-0.06%) from 98.34%
25619921491

push

github

web-flow
Merge pull request #432 from domix/feature/option/enhancements/part-1

fix(option): null-check gaps, ofNullable misuse, sequenceCollector type, and raw casts

1089 of 1131 branches covered (96.29%)

Branch coverage included in aggregate %.

3078 of 3109 relevant lines covered (99.0%)

5.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.17
core/lib/src/main/java/dmx/fun/Option.java
1
package dmx.fun;
2

3
import java.util.ArrayList;
4
import java.util.Collections;
5
import java.util.List;
6
import java.util.NoSuchElementException;
7
import java.util.Objects;
8
import java.util.Optional;
9
import java.util.concurrent.CompletableFuture;
10
import java.util.function.BiFunction;
11
import java.util.function.Consumer;
12
import java.util.function.Function;
13
import java.util.function.Predicate;
14
import java.util.function.Supplier;
15
import java.util.stream.Collector;
16
import java.util.stream.Collectors;
17
import java.util.stream.Gatherer;
18
import java.util.stream.Stream;
19
import java.util.stream.StreamSupport;
20
import org.jspecify.annotations.NullMarked;
21
import org.jspecify.annotations.Nullable;
22

23
/**
24
 * A sealed interface representing an optional value with two possible states:
25
 * either a value is present ("Some") or absent ("None").
26
 *
27
 * <p>Unlike {@link Optional}, which is a {@code final} class, {@code Option} is a
28
 * {@code sealed interface} with two record variants — {@link Some} and {@link None} —
29
 * enabling exhaustive pattern matching: the compiler enforces that both states are handled
30
 * in a switch expression without a wildcard arm.
31
 *
32
 * <p>{@code Option<T>} participates in the library's type graph with first-class
33
 * conversion methods: {@link #toResult(Object)}, {@link #toTry(Supplier)},
34
 * {@link #toEither(Object)}. The reverse conversions are symmetric:
35
 * {@link #fromOptional(Optional)}, {@link #fromResult(Result)}, {@link #fromTry(Try)}.
36
 * The design rationale is documented in
37
 * <a href="https://domix.github.io/dmx-fun/adr/adr-015-option-vs-optional/">
38
 * ADR-015 — Option&lt;T&gt; as a custom type instead of java.util.Optional</a>.
39
 *
40
 * <p>This interface is {@link NullMarked}: all types are non-null by default.
41
 *
42
 * @param <Value> The type of the optional value.
43
 */
44
@NullMarked
45
public sealed interface Option<Value> permits Option.Some, Option.None {
46

47
    /**
48
     * Represents a container object that holds a non-null value.
49
     * <p>
50
     * The {@code Some} class is a concrete implementation of the {@code Option}
51
     * interface, used to encapsulate a value that is guaranteed to be non-null.
52
     * Attempting to create an instance of {@code Some} with a {@code null} value
53
     * will throw a {@code NullPointerException}.
54
     *
55
     * @param value   the non-null value encapsulated by this instance
56
     * @param <Value> the type of the non-null value encapsulated by this instance
57
     */
58
    record Some<Value>(Value value) implements Option<Value> {
59
        /**
60
         * Constructs a new {@code Some} instance with the specified non-null value.
61
         *
62
         * @param value the non-null value to be encapsulated by this {@code Some} instance
63
         * @throws NullPointerException if the provided value is {@code null}
64
         */
65
        public Some {
5✔
66
            Objects.requireNonNull(value, "Option.Some cannot hold null. Use Option.none() or Option.ofNullable().");
4✔
67
        }
1✔
68
    }
69

70
    /**
71
     * Represents a variant of the {@link Option} type that signifies the absence of a value.
72
     * <p>
73
     * This record is used to indicate and handle cases where an operation does not produce
74
     * a meaningful or valid result. It serves as an alternative to using {@code null}.
75
     *
76
     * @param <Value> The type of the value that would be held by an {@link Option}, if present.
77
     */
78
    record None<Value>() implements Option<Value> {
3✔
79
        @SuppressWarnings("rawtypes")
80
        static final None INSTANCE = new None<>();
5✔
81
    }
82

83
    // ---------- Factories ----------
84

85
    /**
86
     * Creates a {@link Some} instance that encapsulates the given non-null value.
87
     * Use {@link #ofNullable(Object)} if the value may be {@code null}.
88
     *
89
     * @param <V>   the type of the value to encapsulate
90
     * @param value the non-null value to encapsulate
91
     * @return a {@code Some<V>} wrapping the provided value
92
     * @throws NullPointerException if {@code value} is {@code null} (enforced by {@link Some#Some(Object)})
93
     */
94
    static <V> Option<V> some(V value) {
95
        return new Some<>(value);
5✔
96
    }
97

98
    /**
99
     * Creates an instance of {@link Option} that represents the absence of a value.
100
     *
101
     * @param <V> the type of the value that would be held by the {@link Option}, if present
102
     * @return an {@code Option} instance that signifies no value is present
103
     */
104
    @SuppressWarnings("unchecked")
105
    static <V> Option<V> none() {
106
        return (Option<V>) None.INSTANCE;
2✔
107
    }
108

109
    /**
110
     * Creates an Option instance that encapsulates a given value. If the value is null, it returns a None instance.
111
     *
112
     * @param <V>   the type of the value to encapsulate
113
     * @param value the value to be encapsulated; if null, a None instance is returned
114
     * @return an Option containing the provided value if it is non-null, or a None instance if the value is null
115
     */
116
    static <V> Option<V> ofNullable(@Nullable V value) {
117
        return value == null ? none() : new Some<>(value);
9✔
118
    }
119

120
    /**
121
     * Converts a given {@link Optional} instance into an {@link Option} instance.
122
     *
123
     * @param <V>      the type of the value that may be present in the {@link Optional}
124
     * @param optional the {@link Optional} to be converted; if the {@link Optional} contains a value,
125
     *                 a {@code Some} instance is returned, otherwise a {@code None} instance is returned
126
     * @return an {@link Option} containing the value from the {@link Optional} if it is present,
127
     * or an empty {@code None} instance if the {@link Optional} is empty
128
     */
129
    static <V> Option<V> fromOptional(Optional<V> optional) {
130
        Objects.requireNonNull(optional, "optional");
4✔
131
        return optional.map(Option::some).orElseGet(Option::none);
7✔
132
    }
133

134
    // ---------- Predicates ----------
135

136
    /**
137
     * Determines whether this instance represents a defined value.
138
     *
139
     * @return {@code true} if this instance is of type {@code Some<?>} and holds a value;
140
     * {@code false} if it is of type {@code None} and does not hold a value.
141
     */
142
    default boolean isDefined() {
143
        return this instanceof Some<?>;
3✔
144
    }
145

146
    /**
147
     * Checks if this {@link Option} instance represents the absence of a value.
148
     *
149
     * @return {@code true} if this instance is of type {@code None<?>}, indicating no value is present;
150
     * {@code false} if this instance holds a value.
151
     */
152
    default boolean isEmpty() {
153
        return this instanceof None<?>;
3✔
154
    }
155

156
    // ---------- Accessors ----------
157

158
    /**
159
     * Retrieves the value held by this {@link Option} instance if it is of type {@code Some}.
160
     * If the instance is of type {@code None}, a {@link NoSuchElementException} is thrown.
161
     *
162
     * @return the encapsulated value if this is an instance of {@code Some}
163
     * @throws NoSuchElementException if this is an instance of {@code None}, indicating no value is present
164
     */
165
    default Value get() {
166
        return switch (this) {
12✔
167
            case Some<Value> s -> s.value();
6✔
168
            case None<Value> _ -> throw new NoSuchElementException("No value present. Option is None.");
5✔
169
        };
170
    }
171

172
    /**
173
     * Retrieves the encapsulated value if this {@link Option} instance is of type {@code Some},
174
     * or returns the provided fallback value if this instance is of type {@code None}.
175
     *
176
     * @param fallback the value to return if this instance is {@code None}
177
     * @return the encapsulated value if this instance is a {@code Some}, or the specified fallback value if this instance is {@code None}
178
     */
179
    default Value getOrElse(Value fallback) {
180
        return this instanceof Some<Value>(Value v) ? v : fallback;
17✔
181
    }
182

183
    /**
184
     * Retrieves the encapsulated value if this {@code Option} instance is of type {@code Some},
185
     * or computes and returns a fallback value supplied by the given {@code fallbackSupplier}
186
     * if this instance is of type {@code None}.
187
     *
188
     * @param fallbackSupplier a {@code Supplier} that provides a fallback value
189
     *                         if this instance represents the absence of a value
190
     * @return the encapsulated value if this instance is a {@code Some},
191
     * or the fallback value computed by the {@code fallbackSupplier} if this instance is a {@code None}
192
     */
193
    default Value getOrElseGet(Supplier<? extends Value> fallbackSupplier) {
194
        Objects.requireNonNull(fallbackSupplier, "fallbackSupplier");
4✔
195
        return this instanceof Some<Value>(Value v) ? v : fallbackSupplier.get();
18✔
196
    }
197

198
    /**
199
     * Returns this {@code Option} if it is {@code Some}, otherwise returns {@code alternative}.
200
     *
201
     * @param alternative the fallback {@code Option} to return when this is {@code None};
202
     *                    must not be {@code null}
203
     * @return this instance if {@code Some}, otherwise {@code alternative}
204
     * @throws NullPointerException if {@code alternative} is null
205
     */
206
    @SuppressWarnings("unchecked")
207
    default Option<Value> orElse(Option<? extends Value> alternative) {
208
        Objects.requireNonNull(alternative, "alternative");
4✔
209
        return isDefined() ? this : (Option<Value>) alternative;
7✔
210
    }
211

212
    /**
213
     * Returns this {@code Option} if it is {@code Some}, otherwise evaluates and returns
214
     * the supplier's result. The supplier is <em>not</em> called when this is {@code Some}.
215
     *
216
     * @param alternative a lazy supplier of a fallback {@code Option}; must not return {@code null}
217
     * @return this instance if {@code Some}, or the result of {@code alternative.get()} if {@code None}
218
     * @throws NullPointerException if {@code alternative} is null or returns {@code null}
219
     */
220
    @SuppressWarnings("unchecked")
221
    default Option<Value> orElse(Supplier<? extends Option<? extends Value>> alternative) {
222
        Objects.requireNonNull(alternative, "alternative");
4✔
223
        if (isDefined()) return this;
5✔
224
        return (Option<Value>) Objects.requireNonNull(alternative.get(), "alternative returned null");
7✔
225
    }
226

227
    /**
228
     * Retrieves the encapsulated value if this {@code Option} instance is of type {@code Some},
229
     * or returns {@code null} if this instance is of type {@code None}.
230
     *
231
     * @return the encapsulated value if this instance is a {@code Some}, or {@code null} if this instance is a {@code None}.
232
     */
233
    default Value getOrNull() {
234
        return this instanceof Some<Value>(Value v) ? v : null;
17✔
235
    }
236

237
    /**
238
     * Retrieves the encapsulated value if this {@code Option} instance is of type {@code Some}.
239
     * If the instance is of type {@code None}, it throws an exception provided by the given {@code exceptionSupplier}.
240
     *
241
     * @param exceptionSupplier a {@code Supplier} that provides the exception to be thrown
242
     *                          if this instance is of type {@code None}
243
     * @return the encapsulated value if this instance is of type {@code Some}
244
     * @throws RuntimeException if this instance is of type {@code None},
245
     *                          using the exception provided by {@code exceptionSupplier}
246
     */
247
    default Value getOrThrow(Supplier<? extends RuntimeException> exceptionSupplier) {
248
        Objects.requireNonNull(exceptionSupplier, "exceptionSupplier");
4✔
249
        return switch (this) {
12✔
250
            case Some<Value> s -> s.value();
6✔
251
            case None<Value> _ -> throw exceptionSupplier.get();
4✔
252
        };
253
    }
254

255
    // ---------- Monadic operations ----------
256

257
    /**
258
     * Transforms the current Option using the provided mapping function.
259
     * If the current Option is a Some, applies the provided mapper to its value.
260
     * If the current Option is a None, returns None without applying the mapper.
261
     *
262
     * @param <NewValue> the type of the value in the resulting Option after transformation
263
     * @param mapper     the function to apply to the value if this Option is a Some;
264
     *                   must not be {@code null} and must not return {@code null}
265
     * @return a new Option containing the mapped value if this is a Some, or None if this is a None
266
     * @throws NullPointerException if {@code mapper} is {@code null} or returns {@code null}
267
     */
268
    default <NewValue> Option<NewValue> map(Function<? super Value, ? extends NewValue> mapper) {
269
        Objects.requireNonNull(mapper, "mapper");
4✔
270
        return switch (this) {
12✔
271
            case Some<Value> s -> Option.some(
7✔
272
                Objects.requireNonNull(mapper.apply(s.value()), "map function returned null"));
4✔
273
            case None<Value> _ -> Option.none();
1✔
274
        };
275
    }
276

277
    /**
278
     * Transforms the current Option value using the provided mapping function and flattens the result.
279
     * If the Option is empty (None), it remains empty. Otherwise, it applies the mapping function
280
     * to the encapsulated value and returns the resulting Option.
281
     *
282
     * @param <NewValue> the type of the element contained in the resulting Option
283
     * @param mapper     the function to apply to the encapsulated value, which produces a new Option
284
     * @return a new Option resulting from applying the mapping function and flattening
285
     * @throws NullPointerException if the mapping function returns null
286
     */
287
    default <NewValue> Option<NewValue> flatMap(Function<? super Value, Option<NewValue>> mapper) {
288
        Objects.requireNonNull(mapper, "mapper");
4✔
289
        return switch (this) {
12✔
290
            case Some<Value> s -> Objects.requireNonNull(mapper.apply(s.value()),
12✔
291
                "flatMap mapper must not return null");
292
            case None<Value> _ -> Option.none();
1✔
293
        };
294
    }
295

296
    /**
297
     * Filters the value of this Option based on the provided predicate.
298
     * If this Option is a Some and the predicate evaluates to true, the Option is returned as-is.
299
     * If the predicate evaluates to false, or this Option is a None, an empty Option is returned.
300
     *
301
     * @param predicate the predicate used to test the value inside this Option
302
     * @return an Option containing the value if the predicate evaluates to true, otherwise an empty Option
303
     */
304
    default Option<Value> filter(Predicate<? super Value> predicate) {
305
        Objects.requireNonNull(predicate, "predicate");
4✔
306
        return switch (this) {
12✔
307
            case Some<Value> s -> predicate.test(s.value()) ? this : Option.none();
12✔
308
            case None<Value> _ -> this;
1✔
309
        };
310
    }
311

312
    /**
313
     * Applies the provided action to the value contained in this instance if it is of type Some.
314
     *
315
     * @param action a {@code Consumer} that performs an operation on the contained value
316
     * @return this instance after applying the provided action
317
     */
318
    default Option<Value> peek(Consumer<? super Value> action) {
319
        Objects.requireNonNull(action, "action");
4✔
320
        if (this instanceof Some<Value>(Value v)) {
13✔
321
            action.accept(v);
3✔
322
        }
323
        return this;
2✔
324
    }
325

326
    /**
327
     * Folds the current option into a single value by applying the appropriate function
328
     * depending on whether the option is a {@code Some} or a {@code None}.
329
     *
330
     * @param <Folded> The type of the resulting value after folding.
331
     * @param onNone   A supplier to provide a value in case the option is {@code None}.
332
     * @param onSome   A function to transform the value in case the option is {@code Some}.
333
     * @return The folded value of type {@code Folded} resulting from applying the appropriate
334
     * supplier or function.
335
     */
336
    default <Folded> Folded fold(
337
        Supplier<? extends Folded> onNone,
338
        Function<? super Value, ? extends Folded> onSome
339
    ) {
340
        Objects.requireNonNull(onNone, "onNone");
4✔
341
        Objects.requireNonNull(onSome, "onSome");
4✔
342
        return switch (this) {
12✔
343
            case Some<Value> s -> onSome.apply(s.value());
8✔
344
            case None<Value> _ -> onNone.get();
2✔
345
        };
346
    }
347

348
    /**
349
     * Executes one of the provided actions based on the state of this value.
350
     * If the value is "Some", the provided consumer is executed with the inner value.
351
     * If the value is "None", the provided runnable is executed.
352
     *
353
     * @param onNone the action to execute if the value is "None"
354
     * @param onSome the consumer to execute if the value is "Some", accepting the inner value
355
     */
356
    default void match(Runnable onNone, Consumer<? super Value> onSome) {
357
        Objects.requireNonNull(onNone, "onNone");
4✔
358
        Objects.requireNonNull(onSome, "onSome");
4✔
359
        switch (this) {
11✔
360
            case Some<Value> s -> onSome.accept(s.value());
8✔
361
            case None<Value> _ -> onNone.run();
2✔
362
        }
363
    }
1✔
364

365
    // ---------- Stream interoperability ----------
366

367
    /**
368
     * Returns a stream representation of the current instance.
369
     * If the instance is of type {@code Some<Value>}, the stream contains the value.
370
     * If the instance is of type {@code None<Value>}, the stream is empty.
371
     * <p>
372
     * Java 9+ Optional has stream(); this mirrors that.
373
     * - {@code Some(v) -> Stream.of(v)}
374
     * - {@code None -> Stream.empty()}
375
     *
376
     * @return a stream containing the value if present, or an empty stream if no value exists
377
     */
378
    default Stream<Value> stream() {
379
        return switch (this) {
12✔
380
            case Some<Value> s -> Stream.of(s.value());
7✔
381
            case None<Value> _ -> Stream.empty();
1✔
382
        };
383
    }
384

385
    /**
386
     * Converts the current instance of a value or none container into an {@code Optional}.
387
     *
388
     * @return an {@code Optional} containing the value if the instance is of type {@code Some},
389
     * or an empty {@code Optional} if the instance is of type {@code None}.
390
     */
391
    default Optional<Value> toOptional() {
392
        return switch (this) {
12✔
393
            case Some<Value> s -> Optional.of(s.value());
7✔
394
            case None<Value> _ -> Optional.empty();
1✔
395
        };
396
    }
397

398
    /**
399
     * Converts this {@code Option} into an already-completed {@link CompletableFuture}.
400
     *
401
     * <ul>
402
     *   <li>{@code Some(v)} → {@code CompletableFuture.completedFuture(v)}</li>
403
     *   <li>{@code None} → a future failed with {@link NoSuchElementException}</li>
404
     * </ul>
405
     *
406
     * <p>Example:
407
     * <pre>{@code
408
     * Option.some("hello").toFuture(); // CompletableFuture completed with "hello"
409
     * Option.none().toFuture();        // CompletableFuture failed with NoSuchElementException
410
     * }</pre>
411
     *
412
     * @return a completed or failed {@code CompletableFuture<Value>}
413
     */
414
    default CompletableFuture<Value> toFuture() {
415
        return switch (this) {
12✔
416
            case Some<Value> s -> CompletableFuture.completedFuture(s.value());
7✔
417
            case None<Value> _ -> CompletableFuture.failedFuture(
5✔
418
                new NoSuchElementException("Option is None")
419
            );
420
        };
421
    }
422

423
    /**
424
     * Collects all present (non-empty) values from a stream of {@code Option} instances into an
425
     * unmodifiable list.
426
     *
427
     * @param options a stream of {@code Option} instances
428
     * @param <V>     the type of the values inside the {@code Option} instances
429
     * @return an unmodifiable list containing all present values from the provided stream
430
     */
431
    static <V> List<V> collectPresent(Stream<Option<V>> options) {
432
        return options
3✔
433
            .flatMap(Option::stream)
1✔
434
            .toList();
1✔
435
    }
436

437
    /**
438
     * Creates a collector that transforms a stream of Option objects into a list of values
439
     * by extracting the present values and filtering out any absent values.
440
     *
441
     * @param <V> the type of the values inside the Option objects
442
     * @return a Collector that collects present values into a List
443
     */
444
    static <V> Collector<Option<? extends V>, ?, List<V>> presentValuesToList() {
445
        return Collectors.flatMapping(Option::stream, Collectors.toList());
4✔
446
    }
447

448
    /**
449
     * Returns a {@link Collector} that reduces a {@code Stream<Option<V>>} to a single
450
     * {@code Optional<List<V>>}.
451
     *
452
     * <p>The result is {@link Option#some(Object) Some} only if <em>every</em> element in the
453
     * stream is {@link Option#some(Object) Some}. The first {@link Option#none() None} causes the
454
     * entire result to be {@link Option#none()}. An empty stream yields an empty list wrapped
455
     * in {@code Option.some}.
456
     *
457
     * <p>This mirrors {@link #sequence(Iterable)} but operates as a stream {@link Collector}.
458
     *
459
     * <p>Example:
460
     * <pre>{@code
461
     * Option<List<User>> allFound = ids.stream()
462
     *     .map(userRepo::findById)             // Stream<Option<User>>
463
     *     .collect(Option.sequenceCollector()); // Option<List<User>>
464
     * }</pre>
465
     *
466
     * @param <V> the element type inside each {@code Option}
467
     * @return a collector producing {@code Option<List<V>>}
468
     */
469
    static <V> Collector<Option<V>, ?, Option<List<V>>> sequenceCollector() {
470
        class Acc {
2✔
471
            final ArrayList<V> values = new ArrayList<>();
5✔
472
            boolean hasNone = false;
4✔
473
        }
474
        return Collector.of(
8✔
475
            Acc::new,
4✔
476
            (acc, opt) -> {
477
                Objects.requireNonNull(opt, "sequenceCollector stream element must not be null");
4✔
478
                if (!acc.hasNone) {
3✔
479
                    switch (opt) {
11✔
480
                        case None<V> __ -> acc.hasNone = true;
7✔
481
                        case Some<V> s  -> acc.values.add(s.value());
9✔
482
                    }
483
                }
484
            },
1✔
485
            (a, b) -> {
486
                if (a.hasNone || b.hasNone) { a.hasNone = true; return a; }
11✔
487
                a.values.addAll(b.values);
6✔
488
                return a;
2✔
489
            },
490
            acc -> acc.hasNone
4✔
491
                ? Option.none()
2✔
492
                : Option.some(Collections.unmodifiableList(acc.values))
4✔
493
        );
494
    }
495

496
    // ---------- sequence / traverse ----------
497

498
    /**
499
     * Transforms an iterable of {@code Option<V>} into a single {@code Option<List<V>>}.
500
     * If any element in the input iterable is {@code None}, this method returns {@code Option.none()}.
501
     * If all elements are {@code Some}, the result is {@code Option.some()} containing a list of values.
502
     *
503
     * @param <V>     The type of the values wrapped in the {@code Option}.
504
     * @param options An iterable containing {@code Option<V>} elements to be transformed.
505
     * @return {@code Option.some()} containing a list of values if all elements are {@code Some},
506
     * or {@code Option.none()} if any element is {@code None}.
507
     * @throws NullPointerException If the {@code options} iterable is {@code null} or contains {@code null} elements.
508
     */
509
    static <V> Option<List<V>> sequence(Iterable<Option<V>> options) {
510
        Objects.requireNonNull(options, "options");
4✔
511
        return sequence(StreamSupport.stream(options.spliterator(), false));
6✔
512
    }
513

514
    /**
515
     * Converts a stream of {@link Option} objects into a single {@link Option} containing
516
     * a {@link List} of values, if all {@link Option} instances in the stream are {@link Some}.
517
     * If the stream contains any {@link None}, the result will be {@link Option#none()}.
518
     *
519
     * @param <V>     the type of elements contained in the {@link Option} instances
520
     * @param options a stream of {@link Option} elements to be sequenced
521
     * @return an {@link Option} containing a {@link List} of values if all elements are {@link Some},
522
     * or {@link Option#none()} if any element in the stream is {@link None}
523
     * @throws NullPointerException if the stream or any of its elements is {@code null}
524
     */
525
    static <V> Option<List<V>> sequence(Stream<Option<V>> options) {
526
        Objects.requireNonNull(options, "options");
4✔
527
        try (var gathered = options.gather(Gatherer.<Option<V>, ArrayList<V>, Option<List<V>>>ofSequential(
7✔
528
                ArrayList::new,
529
                (state, element, downstream) -> {
530
                    Objects.requireNonNull(element, "options contains null element (use Option.none() instead)");
4✔
531
                    if (element instanceof None<V>) {
3✔
532
                        downstream.push(Option.none());
4✔
533
                        return false;
2✔
534
                    }
535
                    state.add(((Some<V>) element).value());
6✔
536
                    return true;
2✔
537
                },
538
                (state, downstream) -> downstream.push(Option.some(List.copyOf(state)))
7✔
539
        ))) {
540
            return gathered.findFirst().orElseThrow();
7✔
541
        }
542
    }
543

544
    /**
545
     * Transforms a collection of values of type A into an optional list of values of type B
546
     * by applying a given mapping function to each element in the input collection.
547
     * If the mapping function returns a {@code None} for any element, this method returns {@code Option.none()}.
548
     *
549
     * @param <A>    the type of the elements in the input collection
550
     * @param <B>    the type of the elements in the resulting optional list
551
     * @param values the collection of input values to be transformed, must not be null
552
     * @param mapper the mapping function to apply to each element in the input collection, must not return null or {@code Option.none()} for valid outputs
553
     * @return an {@code Option} containing a list of transformed values if all transformations succeed,
554
     * or {@code Option.none()} if the mapping function produces a {@code None} for any element
555
     * @throws NullPointerException if {@code values} or {@code mapper} is null,
556
     *                              or if the mapping function returns {@code null}
557
     */
558
    static <A, B> Option<List<B>> traverse(Iterable<A> values, Function<? super A, Option<B>> mapper) {
559
        Objects.requireNonNull(values, "values");
4✔
560
        Objects.requireNonNull(mapper, "mapper");
4✔
561
        return traverse(StreamSupport.stream(values.spliterator(), false), mapper);
7✔
562
    }
563

564
    /**
565
     * Transforms a stream of values by applying a mapper function that returns an {@code Option} for each input value.
566
     * If the mapper function returns {@code None} for any value, the entire result is {@code None}.
567
     * Otherwise, returns a {@code Some} wrapping a list of mapped values.
568
     *
569
     * @param <A>    the type of the input elements in the stream
570
     * @param <B>    the type of the elements in the output {@code List}
571
     * @param values the stream of input values to traverse
572
     * @param mapper the mapping function to transform each input value into an {@code Option} of the output type
573
     * @return an {@code Option} containing a {@code List} of transformed values if all inputs are successfully mapped, or {@code None} if the mapper returns {@code None} for any
574
     * value
575
     * @throws NullPointerException if {@code values} or {@code mapper} is null, or if the mapper function returns {@code null} for any input
576
     */
577
    static <A, B> Option<List<B>> traverse(Stream<A> values, Function<? super A, Option<B>> mapper) {
578
        Objects.requireNonNull(values, "values");
4✔
579
        Objects.requireNonNull(mapper, "mapper");
4✔
580
        try (var gathered = values.gather(Gatherer.<A, ArrayList<B>, Option<List<B>>>ofSequential(
8✔
581
                ArrayList::new,
582
                (state, element, downstream) -> {
583
                    Option<B> ob = Objects.requireNonNull(mapper.apply(element), "traverse mapper must not return null");
8✔
584
                    if (ob instanceof None<B>) {
3✔
585
                        downstream.push(Option.none());
4✔
586
                        return false;
2✔
587
                    }
588
                    state.add(((Some<B>) ob).value());
6✔
589
                    return true;
2✔
590
                },
591
                (state, downstream) -> downstream.push(Option.some(List.copyOf(state)))
7✔
592
        ))) {
593
            return gathered.findFirst().orElseThrow();
7✔
594
        }
595
    }
596

597
    // ---------- Interoperability: Option <-> Result / Try ----------
598

599
    /**
600
     * Converts the current option to a {@code Result} instance.
601
     *
602
     * @param errorIfNone the error value to use if the current option is {@code None}; must not be {@code null}
603
     * @param <TError>    the type of the error value.
604
     * @return a {@code Result} containing the value if this is {@code Some}, or an error if this is {@code None}.
605
     * @throws NullPointerException if {@code errorIfNone} is {@code null}
606
     */
607
    default <TError> Result<Value, TError> toResult(TError errorIfNone) {
608
        Objects.requireNonNull(errorIfNone, "errorIfNone");
4✔
609
        return switch (this) {
12✔
610
            case Some<Value> s -> Result.ok(s.value());
7✔
611
            case None<Value> _ -> Result.err(errorIfNone);
2✔
612
        };
613
    }
614

615
    /**
616
     * Converts the current instance to a {@code Try} instance.
617
     *
618
     * @param exceptionSupplier a supplier that provides the exception to be used
619
     *                          when the current instance is {@code None}.
620
     * @return a {@code Try} instance containing the value if the current instance
621
     * is {@code Some}, or a failed {@code Try} with the supplied exception
622
     * if the current instance is {@code None}.
623
     */
624
    default Try<Value> toTry(Supplier<? extends Throwable> exceptionSupplier) {
625
        Objects.requireNonNull(exceptionSupplier, "exceptionSupplier");
4✔
626
        return switch (this) {
12✔
627
            case Some<Value> s -> Try.success(s.value());
7✔
628
            case None<Value> _ -> Try.failure(
2✔
629
                Objects.requireNonNull(exceptionSupplier.get(), "exceptionSupplier returned null")
5✔
630
            );
631
        };
632
    }
633

634
    /**
635
     * Converts this {@code Option} to an {@link Either}.
636
     *
637
     * <p>{@code Some(v)} maps to {@link Either#right(Object)}; {@code None} maps to
638
     * {@link Either#left(Object)} using the supplied left value. Mirrors the
639
     * existing {@link #toResult(Object)} overload for the neutral two-track type.
640
     *
641
     * @param <L>        the left type
642
     * @param leftIfNone the value to use as the {@link Either.Left} when this is {@code None};
643
     *                   must not be {@code null}
644
     * @return an {@code Either<L, Value>} equivalent of this {@code Option}
645
     * @throws NullPointerException if {@code leftIfNone} is {@code null}
646
     */
647
    default <L> Either<L, Value> toEither(L leftIfNone) {
648
        Objects.requireNonNull(leftIfNone, "leftIfNone");
4✔
649
        return switch (this) {
12✔
650
            case Some<Value> s -> Either.right(s.value());
7✔
651
            case None<Value> _ -> Either.left(leftIfNone);
2✔
652
        };
653
    }
654

655
    /**
656
     * Converts a {@link Result} into an {@link Option}.
657
     * <p>
658
     * If the given {@code result} represents a successful value (i.e., {@code isOk()} returns true),
659
     * the value is wrapped in an {@code Option}. Otherwise, {@code Option.none()} is returned.
660
     *
661
     * @param <V>    the type of the value contained in the result
662
     * @param <E>    the type of the error contained in the result
663
     * @param result the {@code Result} to be converted, must not be null
664
     * @return an {@code Option} containing the value from the result if it is successful, or an
665
     * {@code Option.none()} if the result contains an error
666
     */
667
    static <V, E> Option<V> fromResult(Result<? extends V, ? extends E> result) {
668
        Objects.requireNonNull(result, "result");
4✔
669
        return result.isOk() ? Option.some(result.get()) : Option.none();
9✔
670
    }
671

672
    /**
673
     * Converts a {@link Try} instance into an {@link Option}.
674
     * If the {@link Try} is successful, the resulting {@link Option} contains the value.
675
     * If the {@link Try} is a failure, the resulting {@link Option} is empty.
676
     *
677
     * @param t   the {@link Try} instance to convert, must not be null
678
     * @param <V> the type of the value contained in the {@link Try}
679
     * @return an {@link Option} containing the value if the {@link Try} is successful, or an empty {@link Option} if it is a failure
680
     */
681
    static <V> Option<V> fromTry(Try<? extends V> t) {
682
        Objects.requireNonNull(t, "t");
4✔
683
        return t.isSuccess() ? Option.ofNullable(t.get()) : Option.none();
9✔
684
    }
685

686
    /**
687
     * Converts a {@link Try}{@code <Optional<V>>} into an {@link Option}{@code <V>}, flattening
688
     * both layers in a single step.
689
     *
690
     * <ul>
691
     *   <li>{@code Success(Optional.of(v))} → {@code Some(v)}</li>
692
     *   <li>{@code Success(Optional.empty())} → {@code None}</li>
693
     *   <li>{@code Failure(ex)} → {@code None}</li>
694
     * </ul>
695
     *
696
     * @param t   the {@link Try} wrapping an {@link Optional} value, must not be null
697
     * @param <V> the type of the value inside the {@link Optional}
698
     * @return an {@link Option} containing the value if present and the {@link Try} succeeded,
699
     *         or {@link Option#none()} otherwise
700
     */
701
    static <V> Option<V> fromTryOptional(Try<Optional<V>> t) {
702
        Objects.requireNonNull(t, "t");
4✔
703
        return t.isSuccess() ? fromOptional(t.get()) : Option.none();
10✔
704
    }
705

706
    /**
707
     * Converts an {@link Either} into an {@link Option}, keeping only the {@link Either.Right} value.
708
     *
709
     * <ul>
710
     *   <li>{@link Either.Right Right(r)} → {@code Some(r)}</li>
711
     *   <li>{@link Either.Left Left(_)} → {@code None}</li>
712
     * </ul>
713
     *
714
     * <p>This is the static complement of {@link Either#toOption()}: both express the same
715
     * conversion but from different call sites.
716
     *
717
     * @param <L>    the left type (discarded on conversion)
718
     * @param <R>    the right type (preserved as the {@code Option} value)
719
     * @param either the {@link Either} to convert; must not be {@code null}
720
     * @return {@code Some(r)} if {@code either} is {@code Right}, otherwise {@code None}
721
     * @throws NullPointerException if {@code either} is {@code null}
722
     */
723
    static <L, R> Option<R> fromEither(Either<? extends L, ? extends R> either) {
724
        Objects.requireNonNull(either, "either");
4✔
725
        return switch (either) {
12✔
726
            case Either.Right<? extends L, ? extends R> r -> Option.some(r.value());
7✔
727
            case Either.Left<? extends L, ? extends R>  _ -> Option.none();
1✔
728
        };
729
    }
730

731
    // ---------- zip / map2 ----------
732

733
    /**
734
     * Combines the current {@code Option} instance with another {@code Option} instance into a single {@code Option}
735
     * containing a {@link Tuple2} of their values, if both options are non-empty.
736
     *
737
     * @param <B>   the type of the value contained in the other {@code Option}
738
     * @param other the other {@code Option} to combine with
739
     * @return an {@code Option} containing a {@link Tuple2} of the values from both options if both are non-empty,
740
     * otherwise an empty {@code Option}
741
     */
742
    default <B> Option<Tuple2<Value, B>> zip(Option<? extends B> other) {
743
        Objects.requireNonNull(other, "other");
4✔
744
        return zip(this, other);
4✔
745
    }
746

747
    /**
748
     * Combines the values of this Option with the values of another Option using a provided combining function.
749
     *
750
     * @param <B>      the type of the value contained in the other Option
751
     * @param <R>      the type of the result produced by the combining function
752
     * @param other    the other Option to combine with
753
     * @param combiner the function to combine the values from this Option and the other Option
754
     * @return an Option containing the result of applying the combining function to the values,
755
     * or an empty Option if either this Option or the other Option is empty
756
     */
757
    default <B, R> Option<R> zipWith(Option<? extends B> other,
758
                                     BiFunction<? super Value, ? super B, ? extends R> combiner) {
759
        Objects.requireNonNull(other, "other");
4✔
760
        Objects.requireNonNull(combiner, "combiner");
4✔
761
        return map2(this, other, combiner);
5✔
762
    }
763

764
    /**
765
     * Applies {@code mapper} to the value inside this {@code Option} and pairs the original value
766
     * with the result. Returns {@link Option#none()} if this is empty <em>or</em> if the mapper
767
     * returns {@link Option#none()}.
768
     *
769
     * <p>This is the monadic "dependent zip": the second {@code Option} is computed <em>from</em>
770
     * the first value, unlike {@link #zip(Option)} which takes an already-evaluated option.
771
     *
772
     * <pre>{@code
773
     * Option<String> name = Option.some("alice");
774
     * Option<Tuple2<String, Integer>> result =
775
     *     name.zipWith(n -> lookupAge(n));
776
     * // Some(Tuple2("alice", 30)) if lookupAge returns Some(30)
777
     * // None                      if lookupAge returns None
778
     * }</pre>
779
     *
780
     * @param <B>    type of the value produced by {@code mapper}
781
     * @param mapper function that receives this option's value and returns an {@code Option<B>};
782
     *               must not be {@code null}, and must not return {@code null}
783
     * @return {@code Some(Tuple2(thisValue, b))} if both are present, otherwise {@code None}
784
     * @throws NullPointerException if {@code mapper} is {@code null} or if {@code mapper} returns
785
     *                              {@code null}
786
     */
787
    default <B> Option<Tuple2<Value, B>> zipWith(
788
            Function<? super Value, ? extends Option<? extends B>> mapper) {
789
        Objects.requireNonNull(mapper, "mapper");
4✔
790
        return flatMap(v -> {
5✔
791
            Option<? extends B> opt =
2✔
792
                Objects.requireNonNull(mapper.apply(v), "mapper must not return null");
6✔
793
            return opt.map(b -> new Tuple2<>(v, b));
11✔
794
        });
795
    }
796

797
    /**
798
     * Alias for {@link #zipWith(Function)}.
799
     *
800
     * <p>Applies {@code mapper} to the value inside this {@code Option} and pairs the original
801
     * value with the result. Useful when the name {@code flatZip} better communicates intent
802
     * at the call site.
803
     *
804
     * @param <B>    type of the value produced by {@code mapper}
805
     * @param mapper function that receives this option's value and returns an {@code Option<B>};
806
     *               must not be {@code null}, and must not return {@code null}
807
     * @return {@code Some(Tuple2(thisValue, b))} if both are present, otherwise {@code None}
808
     * @throws NullPointerException if {@code mapper} is {@code null} or if {@code mapper} returns
809
     *                              {@code null}
810
     */
811
    default <B> Option<Tuple2<Value, B>> flatZip(
812
            Function<? super Value, ? extends Option<? extends B>> mapper) {
813
        return zipWith(mapper);
4✔
814
    }
815

816
    /**
817
     * Combines this {@code Option} with two others into an {@code Option<Tuple3>}.
818
     * Returns {@link Option#none()} if any of the three is empty.
819
     *
820
     * @param <B> type of the value in {@code b}
821
     * @param <C> type of the value in {@code c}
822
     * @param b   second option; must not be {@code null}
823
     * @param c   third option; must not be {@code null}
824
     * @return {@code Some(Tuple3(v1, v2, v3))} if all three are non-empty, otherwise {@code None}
825
     * @throws NullPointerException if {@code b} or {@code c} is {@code null}
826
     */
827
    default <B, C> Option<Tuple3<Value, B, C>> zip3(Option<? extends B> b, Option<? extends C> c) {
828
        Objects.requireNonNull(b, "b");
4✔
829
        Objects.requireNonNull(c, "c");
4✔
830
        return zip3(this, b, c);
5✔
831
    }
832

833
    /**
834
     * Combines this {@code Option} with two others using a {@link TriFunction}.
835
     * Returns {@link Option#none()} if any of the three is empty.
836
     *
837
     * @param <B>      type of the value in {@code b}
838
     * @param <C>      type of the value in {@code c}
839
     * @param <R>      result type
840
     * @param b        second option; must not be {@code null}
841
     * @param c        third option; must not be {@code null}
842
     * @param combiner function applied to the three values; must not be {@code null}
843
     * @return {@code Some(combiner(v1, v2, v3))} if all three are non-empty, otherwise {@code None}
844
     * @throws NullPointerException if {@code b}, {@code c}, or {@code combiner} is {@code null}
845
     */
846
    default <B, C, R> Option<R> zipWith3(
847
            Option<? extends B> b,
848
            Option<? extends C> c,
849
            TriFunction<? super Value, ? super B, ? super C, ? extends R> combiner) {
850
        Objects.requireNonNull(b, "b");
4✔
851
        Objects.requireNonNull(c, "c");
4✔
852
        Objects.requireNonNull(combiner, "combiner");
4✔
853
        return map3(this, b, c, combiner);
6✔
854
    }
855

856
    /**
857
     * Combines this {@code Option} with three others into an {@code Option<Tuple4>}.
858
     * Returns {@link Option#none()} if any of the four is empty.
859
     *
860
     * @param <B> type of the value in {@code b}
861
     * @param <C> type of the value in {@code c}
862
     * @param <D> type of the value in {@code d}
863
     * @param b   second option; must not be {@code null}
864
     * @param c   third option; must not be {@code null}
865
     * @param d   fourth option; must not be {@code null}
866
     * @return {@code Some(Tuple4(v1, v2, v3, v4))} if all four are non-empty, otherwise {@code None}
867
     * @throws NullPointerException if {@code b}, {@code c}, or {@code d} is {@code null}
868
     */
869
    default <B, C, D> Option<Tuple4<Value, B, C, D>> zip4(
870
            Option<? extends B> b, Option<? extends C> c, Option<? extends D> d) {
871
        Objects.requireNonNull(b, "b");
4✔
872
        Objects.requireNonNull(c, "c");
4✔
873
        Objects.requireNonNull(d, "d");
4✔
874
        return zip4(this, b, c, d);
6✔
875
    }
876

877
    /**
878
     * Combines this {@code Option} with three others using a {@link QuadFunction}.
879
     * Returns {@link Option#none()} if any of the four is empty.
880
     *
881
     * @param <B>      type of the value in {@code b}
882
     * @param <C>      type of the value in {@code c}
883
     * @param <D>      type of the value in {@code d}
884
     * @param <R>      result type
885
     * @param b        second option; must not be {@code null}
886
     * @param c        third option; must not be {@code null}
887
     * @param d        fourth option; must not be {@code null}
888
     * @param combiner function applied to the four values; must not be {@code null}
889
     * @return {@code Some(combiner(v1, v2, v3, v4))} if all four are non-empty, otherwise {@code None}
890
     * @throws NullPointerException if {@code b}, {@code c}, {@code d}, or {@code combiner} is {@code null}
891
     */
892
    default <B, C, D, R> Option<R> zipWith4(
893
            Option<? extends B> b,
894
            Option<? extends C> c,
895
            Option<? extends D> d,
896
            QuadFunction<? super Value, ? super B, ? super C, ? super D, ? extends R> combiner) {
897
        Objects.requireNonNull(b, "b");
4✔
898
        Objects.requireNonNull(c, "c");
4✔
899
        Objects.requireNonNull(d, "d");
4✔
900
        Objects.requireNonNull(combiner, "combiner");
4✔
901
        return map4(this, b, c, d, combiner);
7✔
902
    }
903

904
    /**
905
     * Combines two {@link Option} instances into a single {@link Option} containing a {@link Tuple2} of the values
906
     * if both options are non-empty. If either option is empty, returns an empty {@link Option}.
907
     * <p>
908
     * {@code zip: Option<A> + Option<B> -> Option<Tuple2<A,B>>}
909
     *
910
     * @param <A> the type of the value in the first {@link Option}
911
     * @param <B> the type of the value in the second {@link Option}
912
     * @param a   the first {@link Option} instance, must not be null
913
     * @param b   the second {@link Option} instance, must not be null
914
     * @return an {@link Option} containing a {@link Tuple2} of the values if both options are non-empty,
915
     * otherwise an empty {@link Option}
916
     * @throws NullPointerException if either {@code a} or {@code b} is null
917
     */
918
    static <A, B> Option<Tuple2<A, B>> zip(Option<? extends A> a, Option<? extends B> b) {
919
        Objects.requireNonNull(a, "a");
4✔
920
        Objects.requireNonNull(b, "b");
4✔
921
        return switch (a) {
12✔
922
            case None<?> _ -> Option.none();
2✔
923
            case Some<? extends A> sa -> switch (b) {
14✔
924
                case None<?> _ -> Option.none();
2✔
925
                case Some<? extends B> sb -> Option.some(new Tuple2<>(sa.value(), sb.value()));
11✔
926
            };
927
        };
928
    }
929

930
    /**
931
     * Combines the values of two Option instances using the provided combiner function.
932
     * If either Option is empty (None), the result is an empty Option.
933
     * If both Options contain values, the combiner function is applied and the result is wrapped in an Option.
934
     *
935
     * @param a        the first Option, which may or may not contain a value
936
     * @param b        the second Option, which may or may not contain a value
937
     * @param combiner the function used to combine the values of a and b if both are present
938
     * @param <A>      the type of the value contained in the first Option
939
     * @param <B>      the type of the value contained in the second Option
940
     * @param <R>      the type of the value contained in the resulting Option
941
     * @return an Option containing the result of applying the combiner function to the values of a and b,
942
     * or an empty Option if either a or b is empty
943
     */
944
    static <A, B, R> Option<R> map2(
945
        Option<? extends A> a,
946
        Option<? extends B> b,
947
        BiFunction<? super A, ? super B, ? extends R> combiner
948
    ) {
949
        Objects.requireNonNull(a, "a");
4✔
950
        Objects.requireNonNull(b, "b");
4✔
951
        Objects.requireNonNull(combiner, "combiner");
4✔
952

953
        return switch (a) {
12✔
954
            case None<?> _ -> Option.none();
2✔
955
            case Some<? extends A> sa -> switch (b) {
14✔
956
                case None<?> _ -> Option.none();
2✔
957
                case Some<? extends B> sb -> Option.some(
6✔
958
                    Objects.requireNonNull(combiner.apply(sa.value(), sb.value()), "combiner returned null"));
6✔
959
            };
960
        };
961
    }
962

963
    // ---------- zip3 / map3 ----------
964

965
    /**
966
     * Combines three {@link Option} instances into a single {@link Option} containing a {@link Tuple3}.
967
     * Returns {@link Option#none()} if any of the three is empty.
968
     *
969
     * @param <A> type of the value in {@code a}
970
     * @param <B> type of the value in {@code b}
971
     * @param <C> type of the value in {@code c}
972
     * @param a   first option; must not be {@code null}
973
     * @param b   second option; must not be {@code null}
974
     * @param c   third option; must not be {@code null}
975
     * @return {@code Some(Tuple3(av, bv, cv))} when all three are non-empty, otherwise {@code None}
976
     * @throws NullPointerException if {@code a}, {@code b}, or {@code c} is {@code null}
977
     */
978
    static <A, B, C> Option<Tuple3<A, B, C>> zip3(
979
            Option<? extends A> a,
980
            Option<? extends B> b,
981
            Option<? extends C> c) {
982
        Objects.requireNonNull(a, "a");
4✔
983
        Objects.requireNonNull(b, "b");
4✔
984
        Objects.requireNonNull(c, "c");
4✔
985
        return switch (a) {
12✔
986
            case None<?> _ -> Option.none();
2✔
987
            case Some<? extends A> sa -> switch (b) {
14✔
988
                case None<?> _ -> Option.none();
2✔
989
                case Some<? extends B> sb -> switch (c) {
14✔
990
                    case None<?> _ -> Option.none();
2✔
991
                    case Some<? extends C> sc -> Option.some(new Tuple3<>(sa.value(), sb.value(), sc.value()));
13✔
992
                };
993
            };
994
        };
995
    }
996

997
    /**
998
     * Combines the values of three {@link Option} instances using the provided {@link TriFunction}.
999
     * Returns {@link Option#none()} if any of the three is empty.
1000
     *
1001
     * @param <A>      type of the value in {@code a}
1002
     * @param <B>      type of the value in {@code b}
1003
     * @param <C>      type of the value in {@code c}
1004
     * @param <R>      result type
1005
     * @param a        first option; must not be {@code null}
1006
     * @param b        second option; must not be {@code null}
1007
     * @param c        third option; must not be {@code null}
1008
     * @param combiner function applied to the three values; must not be {@code null}
1009
     * @return {@code Some(combiner(av, bv, cv))} when all three are non-empty, otherwise {@code None}
1010
     * @throws NullPointerException if any argument is {@code null}
1011
     */
1012
    static <A, B, C, R> Option<R> map3(
1013
            Option<? extends A> a,
1014
            Option<? extends B> b,
1015
            Option<? extends C> c,
1016
            TriFunction<? super A, ? super B, ? super C, ? extends R> combiner) {
1017
        Objects.requireNonNull(a, "a");
4✔
1018
        Objects.requireNonNull(b, "b");
4✔
1019
        Objects.requireNonNull(c, "c");
4✔
1020
        Objects.requireNonNull(combiner, "combiner");
4✔
1021
        return switch (a) {
12✔
1022
            case None<?> _ -> Option.none();
2✔
1023
            case Some<? extends A> sa -> switch (b) {
14✔
1024
                case None<?> _ -> Option.none();
2✔
1025
                case Some<? extends B> sb -> switch (c) {
14!
1026
                    case None<?> _ -> Option.none();
×
1027
                    case Some<? extends C> sc -> Option.some(
6✔
1028
                        Objects.requireNonNull(combiner.apply(sa.value(), sb.value(), sc.value()), "combiner returned null"));
8✔
1029
                };
1030
            };
1031
        };
1032
    }
1033

1034
    // ---------- zip4 / map4 ----------
1035

1036
    /**
1037
     * Combines four {@link Option} instances into a single {@link Option} containing a {@link Tuple4}.
1038
     * Returns {@link Option#none()} if any of the four is empty.
1039
     *
1040
     * @param <A> type of the value in {@code a}
1041
     * @param <B> type of the value in {@code b}
1042
     * @param <C> type of the value in {@code c}
1043
     * @param <D> type of the value in {@code d}
1044
     * @param a   first option; must not be {@code null}
1045
     * @param b   second option; must not be {@code null}
1046
     * @param c   third option; must not be {@code null}
1047
     * @param d   fourth option; must not be {@code null}
1048
     * @return {@code Some(Tuple4(av, bv, cv, dv))} when all four are non-empty, otherwise {@code None}
1049
     * @throws NullPointerException if {@code a}, {@code b}, {@code c}, or {@code d} is {@code null}
1050
     */
1051
    static <A, B, C, D> Option<Tuple4<A, B, C, D>> zip4(
1052
            Option<? extends A> a,
1053
            Option<? extends B> b,
1054
            Option<? extends C> c,
1055
            Option<? extends D> d) {
1056
        Objects.requireNonNull(a, "a");
4✔
1057
        Objects.requireNonNull(b, "b");
4✔
1058
        Objects.requireNonNull(c, "c");
4✔
1059
        Objects.requireNonNull(d, "d");
4✔
1060
        return switch (a) {
12✔
1061
            case None<?> _ -> Option.none();
2✔
1062
            case Some<? extends A> sa -> switch (b) {
14✔
1063
                case None<?> _ -> Option.none();
2✔
1064
                case Some<? extends B> sb -> switch (c) {
14✔
1065
                    case None<?> _ -> Option.none();
2✔
1066
                    case Some<? extends C> sc -> switch (d) {
14✔
1067
                        case None<?> _ -> Option.none();
2✔
1068
                        case Some<? extends D> sd -> Option.some(new Tuple4<>(sa.value(), sb.value(), sc.value(), sd.value()));
15✔
1069
                    };
1070
                };
1071
            };
1072
        };
1073
    }
1074

1075
    /**
1076
     * Combines the values of four {@link Option} instances using the provided {@link QuadFunction}.
1077
     * Returns {@link Option#none()} if any of the four is empty.
1078
     *
1079
     * @param <A>      type of the value in {@code a}
1080
     * @param <B>      type of the value in {@code b}
1081
     * @param <C>      type of the value in {@code c}
1082
     * @param <D>      type of the value in {@code d}
1083
     * @param <R>      result type
1084
     * @param a        first option; must not be {@code null}
1085
     * @param b        second option; must not be {@code null}
1086
     * @param c        third option; must not be {@code null}
1087
     * @param d        fourth option; must not be {@code null}
1088
     * @param combiner function applied to the four values; must not be {@code null}
1089
     * @return {@code Some(combiner(av, bv, cv, dv))} when all four are non-empty, otherwise {@code None}
1090
     * @throws NullPointerException if any argument is {@code null}
1091
     */
1092
    static <A, B, C, D, R> Option<R> map4(
1093
            Option<? extends A> a,
1094
            Option<? extends B> b,
1095
            Option<? extends C> c,
1096
            Option<? extends D> d,
1097
            QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> combiner) {
1098
        Objects.requireNonNull(a, "a");
4✔
1099
        Objects.requireNonNull(b, "b");
4✔
1100
        Objects.requireNonNull(c, "c");
4✔
1101
        Objects.requireNonNull(d, "d");
4✔
1102
        Objects.requireNonNull(combiner, "combiner");
4✔
1103
        return switch (a) {
12✔
1104
            case None<?> _ -> Option.none();
2✔
1105
            case Some<? extends A> sa -> switch (b) {
14✔
1106
                case None<?> _ -> Option.none();
2✔
1107
                case Some<? extends B> sb -> switch (c) {
14!
1108
                    case None<?> _ -> Option.none();
×
1109
                    case Some<? extends C> sc -> switch (d) {
14!
1110
                        case None<?> _ -> Option.none();
×
1111
                        case Some<? extends D> sd -> Option.some(
6✔
1112
                            Objects.requireNonNull(combiner.apply(sa.value(), sb.value(), sc.value(), sd.value()), "combiner returned null"));
10✔
1113
                    };
1114
                };
1115
            };
1116
        };
1117
    }
1118

1119
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc