io.rouz.task.TaskContext Maven / Gradle / Ivy
package io.rouz.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Stream;
import io.rouz.task.context.AsyncContext;
import io.rouz.task.context.InMemImmediateContext;
import io.rouz.task.dsl.TaskBuilder.F0;
/**
* A context for controlling {@link Task} evaluation and {@link Value} computation.
*/
public interface TaskContext {
Logger LOG = LoggerFactory.getLogger(TaskContext.class);
/**
* The entry point for evaluating a {@link Value} from a {@link Task}.
*
* All upstreams to the task will also be evaluated through this same method.
*
* The implementation should define how and where evaluation happens.
*
* This method is a good place to define how for instance value memoization happens.
*
* @param task The task to evaluate
* @param The type of the task result
* @return A value of the task result
*/
default Value evaluate(Task task) {
return task.code().eval(this);
}
/**
* Invoke the process function of a task.
*
* This method will be called when the process function of a task is ready to be invoked. This
* gives this {@link TaskContext} the responsibility of invoking user code. By overriding this
* method, one can intercept the evaluation flow just at the moment between inputs being ready
* and when the user supplied function for task processing is being invoked.
*
* The default implementation will simply invoke the function immediately.
*
* Interception of curried process functions will gate the innermost function, while
* for regular arity 1-3 process functions gating happens around the whole function.
*
* todo: reconsider difference between arity and curried function interception.
*
* @param taskId The id of the task being invoked
* @param processFn A lazily evaluated handle to the process function
* @param The task value type
* @return The value of the process function invocation
*/
default Value invokeProcessFn(TaskId taskId, F0> processFn) {
return processFn.get();
}
/**
* Create a {@link Value} with semantics defined by this {@link TaskContext}
*
* @param value A value value supplier
* @param The type of the value
* @return A value with added semantics
*/
Value value(F0 value);
/**
* Create a {@link Value} with semantics defined by this {@link TaskContext}
*
* @param value An actual value to wrap
* @param The type of the value
* @return A value with added semantics
*/
default Value immediateValue(T value) {
return value(() -> value);
}
/**
* Create a promise for a value that can be fulfilled somewhere else.
*
* @param The type of the promised value
* @return A promise
*/
Promise promise();
/**
* A {@link Collector} that collects a {@link Stream} of {@link Value}s into a {@link Value}
* of a {@link List}.
*
* The semantics of joining {@link Value}s is decided by this {@link TaskContext}.
*/
default Collector, ?, Value>> toValueList() {
return Collector.of(
ArrayList::new, List::add, (a,b) -> { a.addAll(b); return a; },
ValueFold.inContext(this));
}
/**
* A wrapped value with additional semantics for how the enclosed value becomes available and
* how computations on that value are executed.
*
* Value is a Monad and the implementor should minimally need to implement
* {@link TaskContext#value(F0)}, {@link Value#flatMap(Function)} and
* {@link Value#consume(Consumer)} to get a working context with it's associated value type.
*
* @param The enclosed type
*/
interface Value {
/**
* The {@link TaskContext} that created this value.
*
* @return The context
*/
TaskContext context();
/**
* Consume the enclosed value.
*
* @param consumer The code that should consume the value
*/
void consume(Consumer consumer);
/**
* Map the enclosed value through a function and return a {@link Value} enclosing that result.
*
* @param fn The function to map the enclosed value through
* @param The type of the new enclosed value
* @return A new value with a different type
*/
default Value map(Function super T, ? extends U> fn) {
return flatMap(fn.andThen(context()::immediateValue));
}
/**
* Map the enclosed value through a function that return another {@link Value}.
*
* The returned other value could be from a different {@link TaskContext} so the implementor of
* this context should take care of how to bridge the value semantics to the returned value.
*
* @param fn The function to map the enclosed value through
* @param The type of the new enclosed value
* @return A new value with a different type
*/
Value flatMap(Function super T, ? extends Value extends U>> fn);
}
/**
* A promise for a {@link Value} that is supposed to be {@link #set(Object)}.
*
* This is supposed to be used where the processing for producing a value happens in a different
* environment but is needed by values produced in this context.
*
* @param The type of the promised value
*/
interface Promise {
/**
* The value for this promise. When this promise is fulfilled, the value will become available.
*
* @return The value corresponding to this promise
*/
Value value();
/**
* Fulfill the promise.
*
* @param value The value to fulfill the promise with
* @throws IllegalStateException if the promise was already fulfilled
*/
void set(T value);
// todo: set error
}
/**
* Create a default, in-memory, immediate {@link TaskContext}. The values produced by this
* context should behave like synchronously created values. All graph memoization is done
* completely in memory.
*
* @return The context
*/
static TaskContext inmem() {
return InMemImmediateContext.create();
}
/**
* Create an asynchronous {@link TaskContext} that executes all evaluation on the given
* {@link ExecutorService}.
*
* @param executorService The executor service to run evaluations on
* @return The asynchronous context
*/
static TaskContext async(ExecutorService executorService) {
return AsyncContext.create(executorService);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy