All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rouz.flo.TaskContext Maven / Gradle / Ivy

There is a newer version: 0.0.5
Show newest version
package io.rouz.flo;

import static io.rouz.flo.TaskContextWithTask.withTask;

import io.rouz.flo.context.AsyncContext;
import io.rouz.flo.context.InMemImmediateContext;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 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 evaluateInternal(task, withTask(this, task)); } /** * A variant of {@link #evaluate(Task)} that allows the caller to specify the {@link TaskContext} * that should be used within the graph during evaluation. * *

This is intended to be called from {@link TaskContext} implementations that form a * composition of other contexts. * * @param task The task to evaluate * @param context The context to use in further evaluation * @param The type of the task result * @return A value of the task result */ default Value evaluateInternal(Task task, TaskContext context) { return task.code().eval(context); } /** * 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, Fn> processFn) { return processFn.get(); } /** * When called from within any of the functions passed to {@code processWithContext}, this * method will return the {@link Task} currently being processed. Otherwise an empty value * will be returned. * *

The return value of this method is stable for each instance of {@link TaskContext} that is * passed into the process functions. Calls from multiple threads will see the same result as * longs as the calls are made to the same instance. * * @return The task that is being evaluated or empty if called from outside of a process function */ default Optional> currentTask() { return Optional.empty(); } /** * 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(Fn 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 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(Fn)}, {@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); /** * Consume any error the occurred while constructing the enclosed value. * * @param errorConsumer The code that should consume the error */ void onFail(Consumer errorConsumer); /** * 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 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> 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); /** * Fail the promise. * * @param throwable The exception that is the cause of the failure */ void fail(Throwable throwable); } /** * 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 Executor}. * * @param executor The executor to run evaluations on * @return The asynchronous context */ static TaskContext async(Executor executor) { return AsyncContext.create(executor); } }