Skip to content

Commit 36d8587

Browse files
authored
feat: introduce Either (#572)
1 parent 88aa3f2 commit 36d8587

File tree

12 files changed

+1392
-0
lines changed

12 files changed

+1392
-0
lines changed

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- [Psl\DataStructure](./component/data-structure.md)
2626
- [Psl\DateTime](./component/date-time.md)
2727
- [Psl\Dict](./component/dict.md)
28+
- [Psl\Either](./component/either.md)
2829
- [Psl\Encoding\Base64](./component/encoding-base64.md)
2930
- [Psl\Encoding\Hex](./component/encoding-hex.md)
3031
- [Psl\Env](./component/env.md)

docs/component/either.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!--
2+
This markdown file was generated using `docs/documenter.php`.
3+
4+
Any edits to it will likely be lost.
5+
-->
6+
7+
[*index](./../README.md)
8+
9+
---
10+
11+
### `Psl\Either` Component
12+
13+
#### `Interfaces`
14+
15+
- [Either](./../../src/Psl/Either/Either.php#L24)
16+
17+
#### `Classes`
18+
19+
- [Left](./../../src/Psl/Either/Left.php#L20)
20+
- [Right](./../../src/Psl/Either/Right.php#L20)
21+
22+

docs/documenter.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ function get_all_components(): array
193193
'Psl\\DataStructure',
194194
'Psl\\DateTime',
195195
'Psl\\Dict',
196+
'Psl\\Either',
196197
'Psl\\Encoding\\Base64',
197198
'Psl\\Encoding\\Hex',
198199
'Psl\\Env',

src/Psl/Either/Either.php

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Psl\Either;
6+
7+
use Closure;
8+
use Psl\Comparison;
9+
use Psl\Option;
10+
11+
/**
12+
* Represents a value of one of two possible types (a disjoint union).
13+
*
14+
* An instance of Either is either a {@see Left} or a {@see Right}.
15+
*
16+
* By convention, Left represents the failure/error case and Right represents the success case.
17+
*
18+
* @template TLeft
19+
* @template TRight
20+
*
21+
* @extends Comparison\Comparable<Either<TLeft, TRight>>
22+
* @extends Comparison\Equable<Either<TLeft, TRight>>
23+
*/
24+
interface Either extends Comparison\Comparable, Comparison\Equable
25+
{
26+
/**
27+
* Returns true if this is a Right value.
28+
*
29+
* @return bool
30+
*
31+
* @psalm-mutation-free
32+
*/
33+
public function isRight(): bool;
34+
35+
/**
36+
* Returns true if this is a Left value.
37+
*
38+
* @return bool
39+
*
40+
* @psalm-mutation-free
41+
*/
42+
public function isLeft(): bool;
43+
44+
/**
45+
* Returns the contained Right value.
46+
*
47+
* @throws Exception\LeftException If this is a Left.
48+
*
49+
* @return TRight
50+
*
51+
* @psalm-mutation-free
52+
*/
53+
public function getRight(): mixed;
54+
55+
/**
56+
* Returns the contained Left value.
57+
*
58+
* @throws Exception\RightException If this is a Right.
59+
*
60+
* @return TLeft
61+
*
62+
* @psalm-mutation-free
63+
*/
64+
public function getLeft(): mixed;
65+
66+
/**
67+
* Returns the contained Right value, or the provided default.
68+
*
69+
* @note Arguments passed are eagerly evaluated; use {@see getRightOrElse()} for lazy evaluation.
70+
*
71+
* @template T
72+
*
73+
* @param T $default
74+
*
75+
* @return TRight|T
76+
*
77+
* @psalm-mutation-free
78+
*/
79+
public function getRightOr(mixed $default): mixed;
80+
81+
/**
82+
* Returns the contained Left value, or the provided default.
83+
*
84+
* @note Arguments passed are eagerly evaluated; use {@see getLeftOrElse()} for lazy evaluation.
85+
*
86+
* @template T
87+
*
88+
* @param T $default
89+
*
90+
* @return TLeft|T
91+
*
92+
* @psalm-mutation-free
93+
*/
94+
public function getLeftOr(mixed $default): mixed;
95+
96+
/**
97+
* Returns the contained Right value, or computes it from the Left value using the given closure.
98+
*
99+
* @template TResult
100+
*
101+
* @param (Closure(TLeft): TResult) $closure
102+
*
103+
* @param-immediately-invoked-callable $closure
104+
*
105+
* @return TRight|TResult
106+
*/
107+
public function getRightOrElse(Closure $closure): mixed;
108+
109+
/**
110+
* Returns the contained Left value, or computes it from the Right value using the given closure.
111+
*
112+
* @template TResult
113+
*
114+
* @param (Closure(TRight): TResult) $closure
115+
*
116+
* @param-immediately-invoked-callable $closure
117+
*
118+
* @return TLeft|TResult
119+
*/
120+
public function getLeftOrElse(Closure $closure): mixed;
121+
122+
/**
123+
* Converts the Right value to an Option, returning None if this is a Left.
124+
*
125+
* @return Option\Option<TRight>
126+
*
127+
* @psalm-mutation-free
128+
*/
129+
public function unwrapRight(): Option\Option;
130+
131+
/**
132+
* Converts the Left value to an Option, returning None if this is a Right.
133+
*
134+
* @return Option\Option<TLeft>
135+
*
136+
* @psalm-mutation-free
137+
*/
138+
public function unwrapLeft(): Option\Option;
139+
140+
/**
141+
* Maps an Either by applying a function to the contained value, whether Left or Right.
142+
*
143+
* @template TResult
144+
*
145+
* @param (Closure(TLeft|TRight): TResult) $closure
146+
*
147+
* @param-immediately-invoked-callable $closure
148+
*
149+
* @return Either<TResult, TResult>
150+
*/
151+
public function map(Closure $closure): Either;
152+
153+
/**
154+
* Maps an Either by applying a function to the contained Right value,
155+
* leaving a Left value untouched.
156+
*
157+
* @template TResult
158+
*
159+
* @param (Closure(TRight): TResult) $closure
160+
*
161+
* @param-immediately-invoked-callable $closure
162+
*
163+
* @return Either<TLeft, TResult>
164+
*/
165+
public function mapRight(Closure $closure): Either;
166+
167+
/**
168+
* Maps an Either by applying a function to the contained Left value,
169+
* leaving a Right value untouched.
170+
*
171+
* @template TResult
172+
*
173+
* @param (Closure(TLeft): TResult) $closure
174+
*
175+
* @param-immediately-invoked-callable $closure
176+
*
177+
* @return Either<TResult, TRight>
178+
*/
179+
public function mapLeft(Closure $closure): Either;
180+
181+
/**
182+
* Applies a function to the contained value and returns the resulting Either.
183+
*
184+
* @template TResultLeft
185+
* @template TResultRight
186+
*
187+
* @param (Closure(TLeft|TRight): Either<TResultLeft, TResultRight>) $closure
188+
*
189+
* @param-immediately-invoked-callable $closure
190+
*
191+
* @return Either<TResultLeft, TResultRight>
192+
*/
193+
public function flatMap(Closure $closure): Either;
194+
195+
/**
196+
* Applies a function to the contained Right value and returns the resulting Either,
197+
* leaving a Left value untouched.
198+
*
199+
* @template TResultLeft
200+
* @template TResultRight
201+
*
202+
* @param (Closure(TRight): Either<TResultLeft, TResultRight>) $closure
203+
*
204+
* @param-immediately-invoked-callable $closure
205+
*
206+
* @return Either<TLeft|TResultLeft, TResultRight>
207+
*/
208+
public function flatMapRight(Closure $closure): Either;
209+
210+
/**
211+
* Applies a function to the contained Left value and returns the resulting Either,
212+
* leaving a Right value untouched.
213+
*
214+
* @template TResultLeft
215+
* @template TResultRight
216+
*
217+
* @param (Closure(TLeft): Either<TResultLeft, TResultRight>) $closure
218+
*
219+
* @param-immediately-invoked-callable $closure
220+
*
221+
* @return Either<TResultLeft, TRight|TResultRight>
222+
*/
223+
public function flatMapLeft(Closure $closure): Either;
224+
225+
/**
226+
* Matches the contained value with the provided closures and returns the result.
227+
*
228+
* The right closure is the first parameter (happy path first),
229+
* consistent with {@see \Psl\Result\ResultInterface::proceed()} and {@see Option\Option::proceed()}.
230+
*
231+
* @template TResult
232+
*
233+
* @param (Closure(TRight): TResult) $right A closure called when the Either is Right.
234+
*
235+
* @param-immediately-invoked-callable $right
236+
*
237+
* @param (Closure(TLeft): TResult) $left A closure called when the Either is Left.
238+
*
239+
* @param-immediately-invoked-callable $left
240+
*
241+
* @return TResult
242+
*/
243+
public function proceed(Closure $right, Closure $left): mixed;
244+
245+
/**
246+
* Applies a function to the contained value and returns the original Either.
247+
*
248+
* @param (Closure(TLeft|TRight): mixed) $closure
249+
*
250+
* @param-immediately-invoked-callable $closure
251+
*
252+
* @return Either<TLeft, TRight>
253+
*/
254+
public function apply(Closure $closure): Either;
255+
256+
/**
257+
* Swaps the Left and Right sides of this Either.
258+
*
259+
* @return Either<TRight, TLeft>
260+
*
261+
* @psalm-mutation-free
262+
*/
263+
public function swap(): Either;
264+
265+
/**
266+
* Returns true if this is a Right containing the given value.
267+
*
268+
* @psalm-mutation-free
269+
*/
270+
public function containsRight(mixed $value): bool;
271+
272+
/**
273+
* Returns true if this is a Left containing the given value.
274+
*
275+
* @psalm-mutation-free
276+
*/
277+
public function containsLeft(mixed $value): bool;
278+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Psl\Either\Exception;
6+
7+
use Psl\Exception;
8+
9+
interface ExceptionInterface extends Exception\ExceptionInterface {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Psl\Either\Exception;
6+
7+
use Psl\Exception\UnderflowException;
8+
9+
final class LeftException extends UnderflowException implements ExceptionInterface {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Psl\Either\Exception;
6+
7+
use Psl\Exception\UnderflowException;
8+
9+
final class RightException extends UnderflowException implements ExceptionInterface {}

0 commit comments

Comments
 (0)