Skip to content

Commit 7bc9def

Browse files
committed
Adding the ability to supply an IO with externally managed futures
1 parent 6dc9220 commit 7bc9def

File tree

3 files changed

+89
-1
lines changed

3 files changed

+89
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1212
- `MapLens#asCopy` has overload taking copy function
1313
- `MapLens#valueAt` has overload taking copy function
1414
- `SortWith` for sorting an `Iterable` given a `Comparator` over its elements
15+
- `IO#externallyManaged`, for supplying an `IO` with externally-managed futures
1516

1617
### Fixed
1718
- issue where certain ways to compose `Effect`s unintentionally nullified the effect

src/main/java/com/jnape/palatable/lambda/io/IO.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
import com.jnape.palatable.lambda.functor.Applicative;
66
import com.jnape.palatable.lambda.monad.Monad;
77

8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.Executor;
810
import java.util.function.Function;
11+
import java.util.function.Supplier;
912

1013
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
14+
import static com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier.checked;
15+
import static java.util.concurrent.CompletableFuture.supplyAsync;
1116

1217
/**
1318
* A {@link Monad} representing some effectful computation to be performed.
@@ -17,12 +22,38 @@
1722
public interface IO<A> extends Monad<A, IO<?>> {
1823

1924
/**
20-
* Run the effect represented by this {@link IO} instance
25+
* Run the effect represented by this {@link IO} instance, blocking the current thread until the effect terminates.
2126
*
2227
* @return the result of the effect
2328
*/
2429
A unsafePerformIO();
2530

31+
/**
32+
* Returns a {@link CompletableFuture} representing the result of this eventual effect. By default, this will
33+
* immediately run the effect in terms of the implicit {@link Executor} available to {@link CompletableFuture}
34+
* (usually the {@link java.util.concurrent.ForkJoinPool}). Note that specific {@link IO} constructions may allow
35+
* this method to delegate to externally-managed {@link CompletableFuture} instead of synthesizing their own.
36+
*
37+
* @return the {@link CompletableFuture} representing this {@link IO}'s eventual result
38+
* @see IO#unsafePerformAsyncIO(Executor)
39+
*/
40+
default CompletableFuture<A> unsafePerformAsyncIO() {
41+
return supplyAsync(this::unsafePerformIO);
42+
}
43+
44+
/**
45+
* Returns a {@link CompletableFuture} representing the result of this eventual effect. By default, this will
46+
* immediately run the effect in terms of the provided {@link Executor}. Note that specific {@link IO}
47+
* constructions may allow this method to delegate to externally-managed {@link CompletableFuture} instead of
48+
* synthesizing their own.
49+
*
50+
* @return the {@link CompletableFuture} representing this {@link IO}'s eventual result
51+
* @see IO#unsafePerformAsyncIO()
52+
*/
53+
default CompletableFuture<A> unsafePerformAsyncIO(Executor executor) {
54+
return supplyAsync(this::unsafePerformIO, executor);
55+
}
56+
2657
/**
2758
* {@inheritDoc}
2859
*/
@@ -116,4 +147,35 @@ static IO<Unit> io(Runnable runnable) {
116147
static <A> IO<A> io(Fn1<Unit, A> fn1) {
117148
return io(() -> fn1.apply(UNIT));
118149
}
150+
151+
/**
152+
* Static factory method for creating an {@link IO} from an externally managed source of
153+
* {@link CompletableFuture completable futures}.
154+
* <p>
155+
* Note that constructing an {@link IO} this way results in no intermediate futures being constructed by either
156+
* {@link IO#unsafePerformAsyncIO()} or {@link IO#unsafePerformAsyncIO(Executor)}, and {@link IO#unsafePerformIO()}
157+
* is synonymous with invoking {@link CompletableFuture#get()} on the externally managed future.
158+
*
159+
* @param supplier the source of externally managed {@link CompletableFuture completable futures}
160+
* @param <A> the result type
161+
* @return the {@link IO}
162+
*/
163+
static <A> IO<A> externallyManaged(Supplier<CompletableFuture<A>> supplier) {
164+
return new IO<A>() {
165+
@Override
166+
public A unsafePerformIO() {
167+
return checked(() -> unsafePerformAsyncIO().get()).get();
168+
}
169+
170+
@Override
171+
public CompletableFuture<A> unsafePerformAsyncIO() {
172+
return supplier.get();
173+
}
174+
175+
@Override
176+
public CompletableFuture<A> unsafePerformAsyncIO(Executor executor) {
177+
return supplier.get();
178+
}
179+
};
180+
}
119181
}

src/test/java/com/jnape/palatable/lambda/io/IOTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@
99
import testsupport.traits.FunctorLaws;
1010
import testsupport.traits.MonadLaws;
1111

12+
import java.util.concurrent.CompletableFuture;
13+
1214
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
1315
import static com.jnape.palatable.lambda.functions.Fn0.fn0;
16+
import static com.jnape.palatable.lambda.io.IO.externallyManaged;
1417
import static com.jnape.palatable.lambda.io.IO.io;
18+
import static java.util.concurrent.CompletableFuture.completedFuture;
19+
import static java.util.concurrent.Executors.newFixedThreadPool;
20+
import static java.util.concurrent.ForkJoinPool.commonPool;
1521
import static org.junit.Assert.assertEquals;
1622

1723
@RunWith(Traits.class)
@@ -29,4 +35,23 @@ public void staticFactoryMethods() {
2935
assertEquals((Integer) 1, io(fn0(() -> 1)).unsafePerformIO());
3036
assertEquals(UNIT, io(() -> {}).unsafePerformIO());
3137
}
38+
39+
@Test(timeout = 100)
40+
public void unsafePerformAsyncIOWithoutExecutor() {
41+
assertEquals((Integer) 1, io(() -> 1).unsafePerformAsyncIO().join());
42+
}
43+
44+
@Test(timeout = 100)
45+
public void unsafePerformAsyncIOWithExecutor() {
46+
assertEquals((Integer) 1, io(() -> 1).unsafePerformAsyncIO(newFixedThreadPool(1)).join());
47+
}
48+
49+
@Test
50+
public void delegatesToExternallyManagedFuture() {
51+
CompletableFuture<Integer> future = completedFuture(1);
52+
IO<Integer> io = externallyManaged(() -> future);
53+
assertEquals((Integer) 1, io.unsafePerformIO());
54+
assertEquals((Integer) 1, io.unsafePerformAsyncIO().join());
55+
assertEquals((Integer) 1, io.unsafePerformAsyncIO(commonPool()).join());
56+
}
3257
}

0 commit comments

Comments
 (0)