1: <?php declare(strict_types=1);
2:
3: namespace TH\Maybe\Option;
4:
5: use TH\DocTest\Attributes\ExamplesSetup;
6: use TH\Maybe\Option;
7: use TH\Maybe\Result;
8: use TH\Maybe\Tests\Helpers\IgnoreUnusedResults;
9: use function TH\Maybe\Internal\isOfAnyClass;
10:
11: /**
12: * Return a `Option\Some` option containing `$value`.
13: *
14: * @template T
15: * @param T $value
16: * @return Option\Some<T>
17: */
18: function some(mixed $value): Option\Some
19: {
20: return new Option\Some($value);
21: }
22:
23: /**
24: * Return a `Option\None` option containing no values.
25: */
26: function none(): Option\None
27: {
28: return Option\None::instance;
29: }
30:
31: /**
32: * Transform a value into an `Option`.
33: * It will be a `Some` option containing `$value` if `$value` is different from `$noneValue` (default `null`)
34: *
35: * # Examples
36: *
37: * ```
38: * self::assertEq(Option\fromValue("fruits"), Option\some("fruits"));
39: * self::assertEq(Option\fromValue(null), Option\none());
40: * ```
41: *
42: * @template U
43: * @param U $value
44: * @return Option<U>
45: */
46: function fromValue(mixed $value, mixed $noneValue = null, bool $strict = true): Option
47: {
48: $same = $strict
49: ? ($value === $noneValue)
50: // @phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators
51: : ($value == $noneValue); // @phpstan-ignore equal.notAllowed
52:
53: /** @var Option<U> */
54: return $same
55: ? Option\none()
56: : Option\some($value);
57: }
58:
59: /**
60: * Execute a callable and transform the result into an `Option`.
61: * It will be a `Some` option containing the result if it is different from `$noneValue` (default `null`).
62: *
63: * # Examples
64: *
65: * Successful execution:
66: *
67: * ```
68: * self::assertEq(Option\of(fn() => "fruits"), Option\some("fruits"));
69: * ```
70: *
71: * Convertion of `null` to `Option\None`:
72: *
73: * ```
74: * self::assertEq(Option\of(fn() => null), Option\none());
75: * ```
76: *
77: * @template U
78: * @param callable():U $callback
79: * @return Option<U>
80: */
81: function of(callable $callback, mixed $noneValue = null, bool $strict = true): Option
82: {
83: return Option\fromValue($callback(), $noneValue, $strict);
84: }
85:
86: /**
87: * Execute a callable and transform the result into an `Option` as `Option\of()` does
88: * but also return `Option\None` if it an exception matching $exceptionClass was thrown.
89: *
90: * # Examples
91: *
92: * Successful execution:
93: *
94: * ```
95: * self::assertEq(Option\tryOf(fn () => strtolower("FRUITS")), Option\some("fruits"));
96: * ```
97: *
98: * Convertion of `null` to `Option\None`:
99: *
100: * ```
101: * self::assertEq(Option\tryOf(fn() => null), Option\none());
102: * ```
103: *
104: * Checked Exception:
105: *
106: * ```
107: * self::assertEq(Option\tryOf(fn () => new \DateTimeImmutable("nope")), Option\none());
108: * ```
109: *
110: * Unchecked Exception:
111: *
112: * ```
113: * self::assertEq(Option\tryOf(fn () => 1 / 0), Option\none());
114: * // @throws DivisionByZeroError Division by zero
115: * ```
116: *
117: * @template U
118: * @template E of \Throwable
119: * @param callable():U $callback
120: * @param list<class-string<E>>|class-string<E> $exceptionClass
121: * @return Option<U>
122: * @throws \Throwable
123: */
124: function tryOf(
125: callable $callback,
126: mixed $noneValue = null,
127: bool $strict = true,
128: array|string $exceptionClass = \Exception::class,
129: ): Option {
130: try {
131: return Option\of($callback, $noneValue, $strict);
132: } catch (\Throwable $th) {
133: if (isOfAnyClass($th, (array) $exceptionClass)) {
134: return Option\none();
135: }
136:
137: throw $th;
138: }
139: }
140:
141: /**
142: * Converts from `Option<Option<T>>` to `Option<T>`.
143: *
144: * # Examples
145: *
146: * ```
147: * $x = Option\Some("vegetables");
148: * self::assertSame(Option\flatten(Option\some($x)), $x);
149: * self::assertSame(Option\flatten(Option\some(Option\none())), Option\none());
150: * self::assertSame(Option\flatten(Option\none()), Option\none());
151: * ```
152: *
153: * @template U
154: * @param Option<Option<U>> $option
155: * @return Option<U>
156: */
157: function flatten(Option $option): Option
158: {
159: return $option instanceof Option\None
160: ? $option
161: /** @phpstan-ignore missingType.checkedException */
162: : $option->unwrap();
163: }
164:
165: /**
166: * Unzips an option containing a tuple of two options.
167: *
168: * If `self` is `Some([a, b])` this method returns `[Some(a), Some(b)]`. Otherwise, `[None, None]` is returned.
169: *
170: * ```
171: * $x = Option\Some("vegetables");
172: * self::assertEq(Option\unzip(Option\some(["a", 2])), [Option\some("a"), Option\some(2)]);
173: * self::assertSame(Option\unzip(Option\none()), [Option\none(), Option\none()]);
174: * ```
175: *
176: * @template U
177: * @template V
178: * @param Option<array{U, V}> $option
179: * @return array{Option<U>, Option<V>}
180: */
181: function unzip(Option $option): array
182: {
183: /** @var array{Option<U>, Option<V>} */
184: return $option->mapOrElse(
185: static fn (array $a): array => [Option\some($a[0]), Option\some($a[1])],
186: static fn (): array => [Option\none(), Option\none()],
187: );
188: }
189:
190: /**
191: * Transposes an `Option` of a `Result` into a `Result` of an `Option`.
192: *
193: * `None` will be mapped to `Ok(None)`.
194: * `Some(Ok(_))` and `Some(Err(_))` will be mapped to `Ok(Some(_))` and `Err(_)`.
195: *
196: * ```
197: * use TH\Maybe\Result;
198: *
199: * self::assertEq(Result\ok(Option\some(4)), Option\transpose(Option\some(Result\ok(4))));
200: * self::assertEq(Result\err("meat"), Option\transpose(Option\some(Result\err("meat"))));
201: * self::assertEq(Option\transpose(Option\none()), Result\ok(Option\none()));
202: * ```
203: *
204: * @template U
205: * @template E
206: * @param Option<Result<U, E>> $option
207: * @return Result<Option<U>, E>
208: */
209: #[ExamplesSetup(IgnoreUnusedResults::class)]
210: function transpose(Option $option): Result
211: {
212: /** @var Result<Option<U>, E> */
213: return $option->mapOrElse(
214: static fn (Result $result) => $result->map(Option\some(...)),
215: static fn () => Result\ok(Option\none()),
216: );
217: }
218: