|
1 | 1 | package com.jnape.palatable.lambda.io; |
2 | 2 |
|
3 | 3 | import com.jnape.palatable.lambda.adt.Either; |
| 4 | +import com.jnape.palatable.lambda.adt.Try; |
4 | 5 | import com.jnape.palatable.lambda.adt.Unit; |
5 | 6 | import com.jnape.palatable.lambda.adt.choice.Choice2; |
6 | 7 | import com.jnape.palatable.lambda.functions.Fn0; |
|
15 | 16 | import java.util.concurrent.CompletableFuture; |
16 | 17 | import java.util.concurrent.Executor; |
17 | 18 |
|
| 19 | +import static com.jnape.palatable.lambda.adt.Try.failure; |
18 | 20 | import static com.jnape.palatable.lambda.adt.Try.trying; |
19 | 21 | import static com.jnape.palatable.lambda.adt.Unit.UNIT; |
20 | 22 | import static com.jnape.palatable.lambda.adt.choice.Choice2.a; |
@@ -81,43 +83,48 @@ public final CompletableFuture<A> unsafePerformAsyncIO() { |
81 | 83 | * @return the guarded {@link IO} |
82 | 84 | */ |
83 | 85 | public final IO<A> exceptionally(Fn1<? super Throwable, ? extends A> recoveryFn) { |
| 86 | + return exceptionallyIO(t -> io(recoveryFn.apply(t))); |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Like {@link IO#exceptionally(Fn1) exceptionally}, but recover the {@link Throwable} via another {@link IO} |
| 91 | + * operation. If both {@link IO IOs} throw, the "cleanup" {@link IO IO's} {@link Throwable} is |
| 92 | + * {@link Throwable#addSuppressed(Throwable) suppressed} by this {@link IO IO's} {@link Throwable}. |
| 93 | + * |
| 94 | + * @param recoveryFn the recovery function |
| 95 | + * @return the guarded {@link IO} |
| 96 | + */ |
| 97 | + public final IO<A> exceptionallyIO(Fn1<? super Throwable, ? extends IO<A>> recoveryFn) { |
84 | 98 | return new IO<A>() { |
85 | 99 | @Override |
86 | 100 | public A unsafePerformIO() { |
87 | | - return trying(IO.this::unsafePerformIO).recover(recoveryFn); |
| 101 | + return trying(IO.this::unsafePerformIO) |
| 102 | + .recover(t -> trying(recoveryFn.apply(t)::unsafePerformIO) |
| 103 | + .fmap(Try::success) |
| 104 | + .recover(t2 -> { |
| 105 | + t.addSuppressed(t2); |
| 106 | + return failure(t); |
| 107 | + }) |
| 108 | + .orThrow()); |
88 | 109 | } |
89 | 110 |
|
90 | 111 | @Override |
91 | 112 | public CompletableFuture<A> unsafePerformAsyncIO(Executor executor) { |
92 | | - return IO.this.unsafePerformAsyncIO(executor).exceptionally(recoveryFn::apply); |
| 113 | + return IO.this.unsafePerformAsyncIO(executor) |
| 114 | + .thenApply(CompletableFuture::completedFuture) |
| 115 | + .exceptionally(t -> recoveryFn.apply(t).unsafePerformAsyncIO(executor) |
| 116 | + .thenApply(CompletableFuture::completedFuture) |
| 117 | + .exceptionally(t2 -> { |
| 118 | + t.addSuppressed(t2); |
| 119 | + return new CompletableFuture<A>() {{ |
| 120 | + completeExceptionally(t); |
| 121 | + }}; |
| 122 | + }).thenCompose(f -> f)) |
| 123 | + .thenCompose(f -> f); |
93 | 124 | } |
94 | 125 | }; |
95 | 126 | } |
96 | 127 |
|
97 | | -// /** |
98 | | -// * Given a function from any {@link Throwable} to the result type <code>A</code>, if this {@link IO} successfully |
99 | | -// * yields a result, return it; otherwise, map the {@link Throwable} to the result type and return that. |
100 | | -// * |
101 | | -// * @param recoveryFn the recovery function |
102 | | -// * @return the guarded {@link IO} |
103 | | -// */ |
104 | | -// public final IO<A> exceptionallyIO(Fn1<? super Throwable, ? extends IO<A>> recoveryFn) { |
105 | | -// return new IO<A>() { |
106 | | -// @Override |
107 | | -// public A unsafePerformIO() { |
108 | | -// return trying(IO.this::unsafePerformIO).recover(t -> recoveryFn.apply(t).unsafePerformIO()) |
109 | | -// } |
110 | | -// |
111 | | -// @Override |
112 | | -// public CompletableFuture<A> unsafePerformAsyncIO(Executor executor) { |
113 | | -//// IO.this.unsafePerformAsyncIO(executor) |
114 | | -//// .thenCombine() |
115 | | -//// .exceptionally(t -> recoveryFn.apply(t).unsafePerformAsyncIO(executor)) |
116 | | -// } |
117 | | -// }; |
118 | | -// } |
119 | | - |
120 | | - |
121 | 128 | /** |
122 | 129 | * Return an {@link IO} that will run <code>ensureIO</code> strictly after running this {@link IO} regardless of |
123 | 130 | * whether this {@link IO} terminates normally, analogous to a finally block. |
|
0 commit comments