Skip to content

Commit 017145f

Browse files
feat: Added Either
1 parent dca2b0c commit 017145f

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

src/main/java/gentle/Either.java

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package gentle;
2+
3+
import lombok.NonNull;
4+
5+
import java.util.function.Consumer;
6+
import java.util.function.Function;
7+
8+
/**
9+
* Represents a value that can be one of two possible types: a {@code Left} or a {@code Right}.
10+
* <p>
11+
* This is commonly used to model computations that may return either a "success" or an "alternative"
12+
* result without implying failure semantics (unlike {@link gentle.Result}). Typical usage patterns
13+
* include:
14+
* <ul>
15+
* <li>Representing two possible branches of logic</li>
16+
* <li>Returning one of two distinct types without using inheritance</li>
17+
* <li>Expressing conversions or parsing where either a parsed value or a fallback is meaningful</li>
18+
* </ul>
19+
*
20+
* <p>Example:
21+
* <pre>{@code
22+
* Either<String, Integer> parseInt(String s) {
23+
* try {
24+
* return Either.right(Integer.parseInt(s));
25+
* } catch (NumberFormatException e) {
26+
* return Either.left("Not a number: " + s);
27+
* }
28+
* }
29+
*
30+
* int value = parseInt("10").mapRight(n -> n * 2).fold(
31+
* err -> 0,
32+
* ok -> ok
33+
* );
34+
* }</pre>
35+
*
36+
* @param <L> the type of the {@code Left} value, typically considered the "alternative" branch
37+
* @param <R> the type of the {@code Right} value, typically considered the "primary" branch
38+
*/
39+
public sealed interface Either<L, R> permits Either.Left, Either.Right {
40+
41+
/**
42+
* @return {@code true} if this is a {@link Left}, otherwise {@code false}
43+
*/
44+
default boolean isLeft() {
45+
return this instanceof Left<L, R>;
46+
}
47+
48+
/**
49+
* @return {@code true} if this is a {@link Right}, otherwise {@code false}
50+
*/
51+
default boolean isRight() {
52+
return this instanceof Right<L, R>;
53+
}
54+
55+
/**
56+
* Transforms the {@code Left} value using the given function, leaving {@code Right} unchanged.
57+
*
58+
* @param f the mapping function
59+
* @param <U> the new type of the {@code Left} value
60+
* @return a new {@code Either} with the mapped {@code Left}, or the same {@code Right}
61+
*/
62+
default <U> Either<U, R> mapLeft(@NonNull Function<? super L, ? extends U> f) {
63+
return switch (this) {
64+
case Left<L, R> v -> left(f.apply(v.value));
65+
case Right<L, R> v -> right(v.value);
66+
};
67+
}
68+
69+
/**
70+
* Transforms the {@code Right} value using the given function, leaving {@code Left} unchanged.
71+
*
72+
* @param f the mapping function
73+
* @param <U> the new type of the {@code Right} value
74+
* @return a new {@code Either} with the mapped {@code Right}, or the same {@code Left}
75+
*/
76+
default <U> Either<L, U> mapRight(@NonNull Function<? super R, ? extends U> f) {
77+
return switch (this) {
78+
case Left<L, R> v -> left(v.value);
79+
case Right<L, R> v -> right(f.apply(v.value));
80+
};
81+
}
82+
83+
/**
84+
* Applies a function to the {@code Right} value that itself returns an {@code Either}.
85+
* <p>
86+
* This allows chaining of computations that may produce one of two outcomes.
87+
*
88+
* @param f the function to apply
89+
* @param <U> the type of the new {@code Right} value
90+
* @return the resulting {@code Either} from the mapping function, or the same {@code Left}
91+
*/
92+
default <U> Either<L, U> flatMap(@NonNull Function<? super R, ? extends Either<L, U>> f) {
93+
return switch (this) {
94+
case Left<L, R> v -> left(v.value);
95+
case Right<L, R> v -> f.apply(v.value);
96+
};
97+
}
98+
99+
/**
100+
* Processes both possible values by applying one of two provided functions.
101+
*
102+
* @param onLeft function applied if this is {@code Left}
103+
* @param onRight function applied if this is {@code Right}
104+
* @param <T> the return type of the result
105+
* @return the result of applying the appropriate function
106+
*/
107+
default <T> T fold(@NonNull Function<? super L, ? extends T> onLeft,
108+
@NonNull Function<? super R, ? extends T> onRight) {
109+
return switch (this) {
110+
case Left<L, R> v -> onLeft.apply(v.value);
111+
case Right<L, R> v -> onRight.apply(v.value);
112+
};
113+
}
114+
115+
/**
116+
* Executes an action if this is {@code Left}, leaving the value unchanged.
117+
*
118+
* @param action action to apply to the left value
119+
* @return this {@code Either}, for fluent chaining
120+
*/
121+
default Either<L, R> peekLeft(@NonNull Consumer<? super L> action) {
122+
if (this instanceof Left<L, R>(L value)) action.accept(value);
123+
return this;
124+
}
125+
126+
/**
127+
* Executes an action if this is {@code Right}, leaving the value unchanged.
128+
*
129+
* @param action action to apply to the right value
130+
* @return this {@code Either}, for fluent chaining
131+
*/
132+
default Either<L, R> peekRight(@NonNull Consumer<? super R> action) {
133+
if (this instanceof Right<L, R>(R value)) action.accept(value);
134+
return this;
135+
}
136+
137+
/**
138+
* Creates a {@code Left} value.
139+
*/
140+
static <L, R> Either<L, R> left(@NonNull L value) {
141+
return new Left<>(value);
142+
}
143+
144+
/**
145+
* Creates a {@code Right} value.
146+
*/
147+
static <L, R> Either<L, R> right(@NonNull R value) {
148+
return new Right<>(value);
149+
}
150+
151+
/**
152+
* Represents the {@code Left} variant of {@code Either}.
153+
*/
154+
record Left<L, R>(@NonNull L value) implements Either<L, R> {}
155+
156+
/**
157+
* Represents the {@code Right} variant of {@code Either}.
158+
*/
159+
record Right<L, R>(@NonNull R value) implements Either<L, R> {}
160+
}

0 commit comments

Comments
 (0)