
fj.control.parallel.Promise Maven / Gradle / Ivy
package fj.control.parallel;
import fj.*;
import static fj.P.p;
import static fj.Function.curry;
import static fj.Function.identity;
import static fj.control.parallel.Actor.actor;
import static fj.control.parallel.Callables.normalise;
import static fj.control.parallel.Actor.queueActor;
import fj.data.Either;
import fj.data.List;
import fj.data.Option;
import static fj.data.Option.none;
import static fj.data.Option.some;
import fj.data.Stream;
import fj.function.Effect1;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Represents a non-blocking future value. Products, functions, and actors, given to the methods on this class,
* are executed concurrently, and the Promise serves as a handle on the result of the computation. Provides monadic
* operations so that future computations can be combined
*
* Author: Runar
*/
public final class Promise {
private final Actor, Actor>, Promise>> actor;
private final Strategy s;
private final CountDownLatch l = new CountDownLatch(1);
private volatile Option v = none();
private final Queue> waiting = new LinkedList<>();
private Promise(final Strategy s, final Actor, Actor>, Promise>> qa) {
this.s = s;
actor = qa;
}
private static Promise mkPromise(final Strategy s) {
final Actor, Actor>, Promise>> q =
queueActor(s, new Effect1, Actor>, Promise>>() {
public void f(final P2, Actor>, Promise> p) {
final Promise snd = p._2();
final Queue> as = snd.waiting;
if (p._1().isLeft()) {
final A a = p._1().left().value()._1();
snd.v = some(a);
snd.l.countDown();
while (!as.isEmpty())
as.remove().act(a);
} else if (snd.v.isNone())
as.add(p._1().right().value());
else
p._1().right().value().act(snd.v.some());
}
});
return new Promise<>(s, q);
}
/**
* Promises to provide the value of the given 1-product, in the future.
* Represents the unit function for promises.
*
* @param s The strategy with which to fulfil the promise.
* @param a The 1-product to evaluate concurrently.
* @return A promise representing the future result of evaluating the given 1-product.
*/
public static Promise promise(final Strategy s, final P1 a) {
final Promise p = mkPromise(s);
p.actor.act(p(Either.left(a), p));
return p;
}
/**
* Provides a first-class unit function for promises.
*
* @param s The strategy with which to fulfil promises.
* @return A function that, given a 1-product, yields a promise of that product's value.
*/
public static F, Promise> promise(final Strategy s) {
return a -> promise(s, a);
}
/**
* Provides a promise to call the given Callable in the future.
*
* @param s The strategy with which to fulfil the promise.
* @param a The Callable to evaluate concurrently.
* @return A promise of a new Callable that will return the result of calling the given Callable.
*/
public static Promise> promise(final Strategy s, final Callable a) {
return promise(s, P.lazy(() -> normalise(a)));
}
/**
* Transforms any function so that it returns a promise of a value instead of an actual value.
* Represents the Kleisli arrow for the Promise monad.
*
* @param s The strategy with which to fulfil the promise.
* @param f The function to turn into a promise-valued function.
* @return The given function transformed into a function that returns a promise.
*/
public static F> promise(final Strategy s, final F f) {
return a -> promise(s, P1.curry(f).f(a));
}
/**
* Promises to send a value to the given actor in the future.
*
* @param a An actor that will receive this Promise's value in the future.
*/
public void to(final Actor a) {
actor.act(p(Either.right(a), this));
}
/**
* Provides a promise to apply the given function to this promise's future value (covariant functor pattern).
*
* @param f The function to apply to this promise's future value.
* @return A promise representing the future result of applying the given function to this promised value.
*/
public Promise fmap(final F f) {
return bind(promise(s, f));
}
/**
* Promotes any function to a transformation between promises (covariant functor pattern).
*
* @param f The function to promote to a transformation between promises.
* @return That function lifted to a function on Promises.
*/
public static F, Promise> fmap_(final F f) {
return a -> a.fmap(f);
}
/**
* Turns a promise of a promise into just a promise. The join function for the Promise monad.
* Promise to give it a Promise of an A, and it will promise you an A in return.
*
* @param p A promise of a promise.
* @return The promised promise.
*/
public static Promise join(final Promise> p) {
final F, Promise> id = identity();
return p.bind(id);
}
/**
* Turns a product of a promise into just a promise. Does not block on the product by calling it,
* but creates a new promise with a final join.
*
* @param s The strategy with which to fulfil the promise.
* @param p A product-1 of a promise to turn into just a promise.
* @return The joined promise.
*/
public static Promise join(final Strategy s, final P1> p) {
return join(promise(s, p));
}
/**
* Binds the given function over this promise, with a final join.
* The bind function for the Promise monad.
*
* @param f The function to bind over this promise.
* @return The result of applying the given function to this promised value.
*/
public Promise bind(final F> f) {
final Promise r = mkPromise(s);
final Actor ab = actor(s, new Effect1() {
public void f(final B b) {
r.actor.act(p(Either.left(p(b)), r));
}
});
to(ab.promise().contramap(f));
return r;
}
/**
* Performs function application within a promise (applicative functor pattern).
*
* @param pf The promised function to apply.
* @return A new promise after applying the given promised function to this promise.
*/
public Promise apply(final Promise> pf) {
return pf.bind(this::fmap);
}
/**
* Binds the given function to this promise and the given promise, with a final join.
*
* @param pb A promise with which to bind the given function.
* @param f The function to apply to the given promised values.
* @return A new promise after performing the map, then final join.
*/
public Promise bind(final Promise pb, final F> f) {
return pb.apply(fmap(f));
}
/**
* Binds the given function to this promise and the given promise, with a final join.
*
* @param p A promise with which to bind the given function.
* @param f The function to apply to the given promised values.
* @return A new promise after performing the map, then final join.
*/
public Promise bind(final P1> p, final F> f) {
return join(s, p).apply(fmap(f));
}
/**
* Promotes a function of arity-2 to a function on promises.
*
* @param f The function to promote.
* @return A function of arity-2 promoted to map over promises.
*/
public static F, F, Promise>> liftM2(final F> f) {
return curry((ca, cb) -> ca.bind(cb, f));
}
/**
* Turns a List of promises into a single promise of a List.
*
* @param s The strategy with which to sequence the promises.
* @param as The list of promises to transform.
* @return A single promise for the given List.
*/
public static Promise> sequence(final Strategy s, final List> as) {
return join(foldRight(s, liftM2(List.cons()), promise(s, p(List.nil()))).f(as));
}
/**
* First-class version of the sequence function through a List.
*
* @param s The strategy with which to sequence a given list of promises.
* @return A function that turns a list of promises into a single promise of a list.
*/
public static F>, Promise>> sequence(final Strategy s) {
return as -> sequence(s, as);
}
/**
* Turns a Stream of promises into a single promise of a Stream.
*
* @param s The strategy with which to sequence the promises.
* @param as The Stream of promises to transform.
* @return A single promise for the given Stream.
*/
public static Promise> sequence(final Strategy s, final Stream> as) {
return join(foldRightS(s, curry((Promise o, P1>> p) -> o.bind(a -> p._1().fmap(Stream.cons_().f(a)))), promise(s, p(Stream.nil()))).f(as));
}
/**
* First-class version of the sequence function through a Stream.
*
* @param s The strategy with which to sequence a given Stream of promises.
* @return A function that turns a list of promises into a single promise of a Stream..
*/
public static F>, Promise>> sequenceS(final Strategy s) {
return as -> sequence(s, as);
}
/**
* Transforms a product of a promise to a promise of a product.
*
* @param s The strategy with which to traverse the promise.
* @param p A product of a promise to traverse.
* @return A promised product.
*/
public static Promise> sequence(final Strategy s, final P1> p) {
return join(promise(s, p)).fmap(P.p1());
}
/**
* Performs a right-fold reduction across a list in constant stack space.
*
* @param s The strategy with which to fold the list.
* @param f The function to apply on each element of the list.
* @param b The beginning value to start the application from.
* @return The final result after the right-fold reduction.
*/
public static F, Promise> foldRight(final Strategy s, final F> f, final B b) {
return new F, Promise>() {
public Promise f(final List as) {
return as.isEmpty() ? promise(s, p(b)) : liftM2(f).f(promise(s, p(as.head()))).f(
join(s, P1.curry(this).f(as.tail())));
}
};
}
/**
* Performs a right-fold reduction across a Stream in constant stack space.
*
* @param s The strategy with which to fold the Stream.
* @param f The function to apply on each element of the Stream.
* @param b The beginning value to start the application from.
* @return The final result after the right-fold reduction.
*/
public static F, Promise> foldRightS(final Strategy s, final F, B>> f,
final B b) {
return new F, Promise>() {
public Promise f(final Stream as) {
return as.isEmpty() ? promise(s, p(b)) : liftM2(f).f(promise(s, p(as.head()))).f(
Promise.join(s, P.lazy(() -> f(as.tail()._1()).fmap(P.p1()))));
}
};
}
/**
* Waits if necessary for the computation to complete, and then retrieves its result.
*
* @return The promised value.
*/
public A claim() {
try {
l.await();
} catch (InterruptedException e) {
throw new Error(e);
}
return v.some();
}
/**
* Waits if necessary for the computation to complete, and then retrieves its result.
*
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @return The promised value, or none if the timeout was reached.
*/
public Option claim(final long timeout, final TimeUnit unit) {
try {
if (l.await(timeout, unit))
return v;
} catch (InterruptedException e) {
throw new Error(e);
}
return none();
}
/**
* Returns true if this promise has been fulfilled.
*
* @return true if this promise has been fulfilled.
*/
public boolean isFulfilled() {
return v.isSome();
}
/**
* Binds the given function across a promise of this promise (Comonad pattern).
*
* @param f A function to apply within a new promise of this promise.
* @return A new promise of the result of applying the given function to this promise.
*/
public Promise cobind(final F, B> f) {
return promise(s, P.lazy(() -> f.f(Promise.this)));
}
/**
* Duplicates this promise to a promise of itself (Comonad pattern).
*
* @return a promise of this promise.
*/
public Promise> cojoin() {
final F, Promise> id = identity();
return cobind(id);
}
/**
* Applies a stream of comonadic functions to this promise, returning a stream of values.
*
* @param fs A stream of functions to apply to this promise.
* @return A stream of the results of applying the given stream of functions to this promise.
*/
public Stream sequenceW(final Stream, B>> fs) {
return fs.isEmpty()
? Stream.nil()
: Stream.cons(fs.head().f(this), () -> sequenceW(fs.tail()._1()));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy