@@ -75,15 +75,17 @@ function err(mixed $value): Result\Err
7575 * ```
7676 *
7777 * @template U
78- * @param callable():U $callback
79- * @return Result<U,\Throwable>
78+ * @template E of \Throwable
79+ * @param callable(mixed...):U $callback
80+ * @param class-string<E> $exceptionClass
81+ * @return Result<U,E>
8082 * @throws \Throwable
8183 */
8284#[ExamplesSetup(IgnoreUnusedResults::class)]
8385function trap (callable $ callback , string $ exceptionClass = \Exception::class): Result
8486{
8587 try {
86- /** @var Result<U,\Throwable > */
88+ /** @var Result<U,E > */
8789 return Result \ok ($ callback ());
8890 } catch (\Throwable $ th ) {
8991 if (\is_a ($ th , $ exceptionClass )) {
@@ -94,6 +96,73 @@ function trap(callable $callback, string $exceptionClass = \Exception::class): R
9496 }
9597}
9698
99+ /**
100+ * Wrap a callable into one that transforms its returned value or thrown exception
101+ * into a `Result` like `Result\trap()` does.
102+ *
103+ * # Examples
104+ *
105+ * Successful execution:
106+ *
107+ * ```
108+ * self::assertEq(Result\ok(3), Result\ify(fn () => 3)());
109+ * ```
110+ *
111+ * Checked exception:
112+ *
113+ * ```
114+ * $x = Result\ify(fn () => new \DateTimeImmutable("2020-30-30 UTC"))();
115+ * self::assertTrue($x->isErr());
116+ * $x->unwrap();
117+ * // @throws Exception Failed to parse time string (2020-30-30 UTC) at position 6 (0): Unexpected character
118+ * ```
119+ *
120+ * Unchecked exception:
121+ *
122+ * ```
123+ * Result\ify(fn () => 1/0)();
124+ * // @throws DivisionByZeroError Division by zero
125+ * ```
126+ *
127+ * Result-ify `strtotime()`:
128+ *
129+ * ```
130+ * $strtotime = Result\ify(
131+ * static fn (...$args)
132+ * => \strtotime(...$args)
133+ * ?: throw new \RuntimeException("Could not convert string to time"),
134+ * );
135+ *
136+ * self::assertEq($strtotime("2015-09-21 UTC midnight")->unwrap(), 1442793600);
137+ *
138+ * $r = $strtotime("nope");
139+ * self::assertTrue($r->isErr());
140+ * $r->unwrap(); // @throws RuntimeException Could not convert string to time
141+ * ```
142+ *
143+ * @template U
144+ * @template E of \Throwable
145+ * @param callable(mixed...):U $callback
146+ * @param class-string<E> $exceptionClass
147+ * @return \Closure(mixed...):Result<U,E>
148+ */
149+ #[ExamplesSetup(IgnoreUnusedResults::class)]
150+ function ify (callable $ callback , string $ exceptionClass = \Exception::class): \Closure
151+ {
152+ return static function (...$ args ) use ($ callback , $ exceptionClass ): Result
153+ {
154+ try {
155+ return Result \ok ($ callback (...$ args ));
156+ } catch (\Throwable $ th ) {
157+ if (\is_a ($ th , $ exceptionClass )) {
158+ return Result \err ($ th );
159+ }
160+
161+ throw $ th ;
162+ }
163+ };
164+ }
165+
97166/**
98167 * Converts from `Result<Result<T, E>, E>` to `Result<T, E>`.
99168 *
0 commit comments