dewit 0.0.1

Define scheduling and execution of code separately from data flow
Documentation
//! A Task is a strategy by which data is transformed. It is not entirely unlike
//! an even-more-restrictive function definition with annotations for use by
//! [`Mode`] implementations for scheduling.
//!
//! For example, here's a trivial function that defines a task:
//!
//! ```rust
//! # use dewit::prelude::*;
//! fn division_is_hard() -> impl Task<'static, (u32, u32), Option<u32>> {
//!     cpu(move |(numerator, denominator)| {
//!         if denominator == 0 {
//!             return None;
//!         }
//!         Some(numerator / denominator)
//!     })
//! }
//! ```
//!
//! Client code can then use and schedule the task as it pleases:
//!
//! ```rust
//! # use dewit::prelude::*;
//! # fn division_is_hard() -> impl Task<'static, (u32, u32), Option<u32>> {
//! #     cpu(move |(numerator, denominator)| {
//! #         if denominator == 0 {
//! #             return None;
//! #         }
//! #         Some(numerator / denominator)
//! #     })
//! # }
//! assert_eq!(
//!     division_is_hard().run_blocking((10, 2)),
//!     Some(5),
//! );
//! ```
//!
//! Or with an async runtime:
//!
//! ```rust
//! # use dewit::prelude::*;
//! # fn division_is_hard() -> impl Task<'static, (u32, u32), Option<u32>> {
//! #     cpu(move |(numerator, denominator)| {
//! #         if denominator == 0 {
//! #             return None;
//! #         }
//! #         Some(numerator / denominator)
//! #     })
//! # }
//! assert_eq!(
//! #   ::pollster::block_on(async move {
//! #       division_is_hard().run_async((10, 3)).await
//! #   }), /*
//!     division_is_hard().run_async((10, 3)).await,
//! #   */
//!     Some(3),
//! );
//! ```
//!
//! The client code does not need to know the internals of how the task is
//! implemented, only how it wants it to be scheduled. Because the basic
//! [`blocking`](crate::combinators::blocking),
//! [`cpu`](crate::combinators::cpu), and [`io`](crate::combinators::io)
//! combinators are themselves tasks, and their combinators construct tasks, you
//! can also arbitrarily chain together tasks:
//!
//! ```rust
//! # use dewit::prelude::*;
//! # fn division_is_hard() -> impl Task<'static, (u32, u32), Option<u32>> {
//! #     cpu(move |(numerator, denominator)| {
//! #         if denominator == 0 {
//! #             return None;
//! #         }
//! #         Some(numerator / denominator)
//! #     })
//! # }
//! fn add_some(increment: u32) -> impl Task<'static, u32, u32> {
//!     cpu(move |val| val + increment)
//! }
//! assert_eq!(
//!     division_is_hard()
//!         .map(add_some(100))
//!         .run_blocking((10, 2)),
//!     Some(105),
//! );
//! ```

use crate::{
    combinators,
    mode::{
        Mode,
        runtime,
    },
};

/// Output of a [`Task`] for the given [`Mode`].
pub type TaskOutput<'src, M, O> = <M as Mode<'src>>::Output<O>;

/// Various strategies by which data is transformed by computation.
pub trait Task<'src, I, O>
where
    O: Send + 'src,
{
    #[doc(hidden)]
    fn go<M>(self, mode: &'src M, input: I) -> TaskOutput<'src, M, O>
    where
        Self: Sized,
        M: Mode<'src> + Send + Sync;

    #[doc(hidden)]
    #[cfg(feature = "blocking")]
    fn go_blocking(
        self,
        mode: &'src runtime::Blocking,
        input: I,
    ) -> TaskOutput<'src, runtime::Blocking, O>;
    #[doc(hidden)]
    #[cfg(feature = "async")]
    fn go_async(self, mode: &'src runtime::Async, input: I) -> TaskOutput<'src, runtime::Async, O>
    where
        'src: 'static;

    /// Evaluate the computation in a blocking fashion on a single thread.
    #[cfg(feature = "blocking")]
    fn run_blocking(self, input: I) -> O
    where
        Self: Sized,
    {
        self.go_blocking(&runtime::Blocking, input)
    }
    /// Evaluate the computation with each closure as a future, yielding a
    /// future.
    #[cfg(feature = "async")]
    fn run_async(self, input: I) -> impl Future<Output = O>
    where
        Self: Sized,
        'src: 'static,
    {
        self.go_async(&runtime::Async, input)
    }

    /// Apply another Task after this one.
    #[inline(always)]
    fn then<BO>(self, next: impl Task<'src, O, BO> + Send + 'src) -> impl Task<'src, I, BO>
    where
        Self: Sized,
        BO: Send + 'src,
    {
        combinators::then(self, next)
    }
}

/// Additional strategies to transform data available when operating on
/// [`Result`] types.
pub trait TaskResultExt<'src, I, E, O>
where
    Self: Task<'src, I, Result<O, E>> + Sized,
    E: Send + 'src,
    O: Send + 'src,
{
    /// Apply another Task after this one, mapping a [`Result`] to another and
    /// passing errors through. Analogous to
    /// [`Result::map`](core::result::Result::map).
    #[inline(always)]
    fn map<BO>(
        self,
        next: impl Task<'src, O, BO> + Send + 'src,
    ) -> impl Task<'src, I, Result<BO, E>>
    where
        BO: Send + 'src,
    {
        combinators::map_result(self, next)
    }

    /// Apply another Task after this one, mapping a [`Result`] to another and
    /// potentially yielding new errors. Analogous to
    /// [`Result::and_then`](core::result::Result::and_then).
    #[inline(always)]
    fn and_then<BO>(
        self,
        next: impl Task<'src, O, Result<BO, E>> + Send + 'src,
    ) -> impl Task<'src, I, Result<BO, E>>
    where
        BO: Send + 'src,
    {
        combinators::and_then_result(self, next)
    }
}

impl<'src, I, E, O, T> TaskResultExt<'src, I, E, O> for T
where
    E: Send + 'src,
    O: Send + 'src,
    T: Task<'src, I, Result<O, E>>,
{
}

/// Additional strategies to transform data available when operating on
/// [`Option`] types.
pub trait TaskOptionExt<'src, I, O>
where
    Self: Task<'src, I, Option<O>> + Sized,
    O: Send + 'src,
{
    /// Apply another Task after this one, mapping a [`Option`] to another and
    /// passing [`None`] through. Analogous to
    /// [`Option::map`](core::option::Option::map).
    #[inline(always)]
    fn map<BO>(self, next: impl Task<'src, O, BO> + Send + 'src) -> impl Task<'src, I, Option<BO>>
    where
        BO: Send + 'src,
    {
        combinators::map_option(self, next)
    }

    /// Apply another Task after this one, mapping a [`Option`] to another and
    /// potentially yielding a new [`None`]. Analogous to
    /// [`Option::and_then`](core::option::Option::and_then).
    #[inline(always)]
    fn and_then<BO>(
        self,
        next: impl Task<'src, O, Option<BO>> + Send + 'src,
    ) -> impl Task<'src, I, Option<BO>>
    where
        BO: Send + 'src,
    {
        combinators::and_then_option(self, next)
    }
}

impl<'src, I, O, T> TaskOptionExt<'src, I, O> for T
where
    O: Send + 'src,
    T: Task<'src, I, Option<O>>,
{
}

/// TODO doc iterext
/// FIXME also support scalar -> vector, vector -> vector, vector -> scalar
pub trait TaskIteratorExt<'src, I, O, IO>
where
    IO: Iterator<Item = O> + Send + 'src,
    Self: Task<'src, I, IO> + Sized,
{
    /// TODO doc itermap
    fn map<BO, IBO>(self, next: impl Task<'src, O, BO> + Clone + Send + 'src) -> impl Task<'src, I, IBO>
    where
        BO: Send + 'src,
        IBO: Iterator<Item = BO> + Send + 'src,
    {
        combinators::map_iterator(self, next)
    }
}

impl<'src, I, O, IO, T> TaskIteratorExt<'src, I, O, IO> for T
where
    O: Send + 'src,
    IO: Iterator<Item = O> + Send + 'src,
    T: Task<'src, I, IO> + Sized,
{
}