pub mod r#async;
pub mod blocking;
use core::{future::Future, ops::ControlFlow};
use crate::never::Never;
// Goal: code using the module shouldn't care if the underlying environment is async or not.
// That is the API should allow yielding without forcing it.
//
// Escaping the effect wrapper requires knowing the concrete context or transforming in the more
// general form which is a Future.
// Async operations cannot be made blocking without issues.
//
// Async and blocking can perform loops. In async the loop will yield between iterations.
// The effective value becomes the loop context. This operation can encode arbitrary folds which
// gives a fully powerful loop construct. The callback effective is allowed to reference the
// context, but the returned value is not.
//
// Async and blocking can perform maps. This is a blocking transform of the value from one form to
// another.
//
// A map that returns an effective for the callback is a then.
//
// The special operation is as_ctx which allows a effective value to be referenced temporarily
// during the operation. This is basically a loop that happens onces.
//
// All effectives can be converted to the effect's erased effective.
//
// An effective can be made from a value.
//
// An effective can be made from a future.
//
// Multiple effectives can be joined together.
//
// E::join((
// || A,
// || B
// ))
pub trait Ss: Send + Sync {}
impl<T: ?Sized + Send + Sync> Ss for T {}
/// Type class for type erased higher-ranked effective types.
#[allow(non_snake_case)]
mod Erased {
use super::*;
/// For all lifetimes `'lt`.
///
/// You should never need to interact with this trait directly.
/// Instead use the [`ErasedEffective`] type alias.
///
/// The `Output` generic type is what the effective will output.
/// The `E` generic type is the effect this effective is for.
/// The `Bound` generic is for higher-ranked implied bounds.
/// See TODO for more info.
pub trait ForLt<'lt, Output: Ss + 'lt, E: Effect, Bound: 'lt> {
/// The effective type with the lifetime `'lt` given.
type Effective: Effective<'lt, Output = Output, Effect = E>;
}
/// A type class for higher-ranked types with an effective concrete type.
///
/// You should never need to interact with this trait directly.
/// Instead use the [`ErasedEffective`] type alias.
///
/// The `Output` generic type is what the effective will output.
/// The `E` generic type is the effect this effective is for.
pub trait Hkt<Output: Ss, E: Effect> {
/// Accessor to inject arbitrary higher-ranked implied bounds.
/// See TODO for more info.
type Hrt<Bound>: for<'a> ForLt<'a, Output, E, &'a (Output, Bound)>;
}
}
/// Type alias to get the concrete [`Effect::Erased`] type.
///
/// The `'lt` lifetime is the minimum lifetime of the returned effective.
/// `Output` is the output type of the effective.
/// `E` is the effect to pull the [`Effect::Erased`] from.
///
/// The `Bound` generic shouldn't be needed or used in most code.
/// It is only needed for adding higher-ranked implied bounds.
/// See TODO for details.
#[rustfmt::skip]
pub type ErasedEffective<'lt, Output, E, Bound = ()> = <
<
<E as Effect>::Erased<Output>
as
Erased::Hkt<Output, E>
>::Hrt<Bound>
as
Erased::ForLt<'lt, Output, E, &'lt (Output, Bound)>
>::Effective;
pub trait BlockOn: Ss + Sized + 'static {
fn block_on<F>(future: F) -> F::Output
where
F: core::future::Future + Ss,
<F as core::future::Future>::Output: Ss;
}
pub trait Effect: Join<Effect = Self> + TryJoin<Effect = Self> + Ss + Sized + 'static {
type BlockOn: BlockOn;
type Erased<T: Ss>: Erased::Hkt<T, Self>;
fn ready<'a, T: Ss + 'a>(value: T) -> ErasedEffective<'a, T, Self>;
fn from_future<'a, F>(future: F) -> ErasedEffective<'a, F::Output, Self>
where
F: Ss + 'a + Future,
F::Output: Ss + 'a;
}
pub trait ReadyExt<'lt, Eff: Effective<'lt, Output = Self>>: Ss + Sized {
fn ready(self) -> Eff;
}
impl<'lt, Eff: Effective<'lt, Output = T>, T: Ss> ReadyExt<'lt, Eff> for T {
fn ready(self) -> Eff {
Eff::ready(self)
}
}
pub trait ConvertShort<T> {
fn convert_short(short: Self) -> T;
}
pub trait FromShort<S = <Self as ShortCircuit>::Short> {
fn from_short(short: S) -> Self;
}
impl<T, S> FromShort<S> for T
where
S: ConvertShort<T>,
{
fn from_short(short: S) -> Self {
S::convert_short(short)
}
}
pub trait ShortCircuit: FromShort {
type Output;
type Short;
fn from_output(output: Self::Output) -> Self;
fn branch(self) -> ControlFlow<Self::Short, Self::Output>;
}
pub fn effective_from_short<'lt, Eff, S>(short: S) -> Eff
where
Eff: Effective<'lt>,
Eff::Output: FromShort<S>,
{
Eff::ready(FromShort::from_short(short))
}
impl<T> ConvertShort<Option<T>> for Option<Never> {
fn convert_short(short: Option<Never>) -> Option<T> {
match short {
None => None,
Some(_) => unreachable!(),
}
}
}
impl<T> ShortCircuit for Option<T> {
type Output = T;
type Short = Option<Never>;
fn branch(self) -> ControlFlow<Self::Short, Self::Output> {
match self {
Some(v) => ControlFlow::Continue(v),
None => ControlFlow::Break(None),
}
}
fn from_output(output: Self::Output) -> Self {
Some(output)
}
}
impl<T, E> ConvertShort<Result<T, E>> for Result<Never, E> {
fn convert_short(short: Result<Never, E>) -> Result<T, E> {
match short {
Err(err) => Err(err),
Ok(_) => unreachable!(),
}
}
}
impl<T, E> ShortCircuit for Result<T, E> {
type Output = T;
type Short = Result<Never, E>;
fn branch(self) -> ControlFlow<Self::Short, Self::Output> {
match self {
Ok(v) => ControlFlow::Continue(v),
Err(err) => ControlFlow::Break(Err(err)),
}
}
fn from_output(output: Self::Output) -> Self {
Ok(output)
}
}
pub trait ResultErrorExt<E> {
fn into_error(self) -> E;
}
impl<E> ResultErrorExt<E> for Result<Never, E> {
fn into_error(self) -> E {
match self {
Ok(_) => unreachable!(),
Err(err) => err,
}
}
}
#[macro_export]
macro_rules! tri {
($expr:expr $(,)?) => {
match $crate::effect::ShortCircuit::branch($expr) {
::core::ops::ControlFlow::Continue(val) => val,
::core::ops::ControlFlow::Break(short) => {
return $crate::effect::effective_from_short(short);
}
}
};
}
pub use tri;
pub trait EffectExt: Effect {
fn repeat_map<'ctx, 'wrap, I, U, F>(input: I, f: F) -> ErasedEffective<'wrap, U, Self>
where
F: 'wrap
+ for<'temp> FnMut(&'temp mut I) -> ErasedEffective<'temp, ControlFlow<U>, Self, &'ctx ()>,
I: Ss + 'wrap,
U: Ss,
F: Ss,
'ctx: 'wrap,
{
Self::ready(input).repeat_map(f)
}
#[inline(always)]
fn as_ctx<'ctx, 'wrap, I, U, F>(input: I, f: F) -> ErasedEffective<'wrap, (I, U), Self>
where
F: 'wrap + for<'temp> FnOnce(&'temp mut I) -> ErasedEffective<'temp, U, Self, &'ctx ()>,
I: Ss + 'wrap,
U: Ss,
F: Ss,
'ctx: 'wrap,
{
Self::ready(input).as_ctx(f)
}
fn as_ctx_map<'ctx, 'wrap, I, U, F>(input: I, f: F) -> ErasedEffective<'wrap, U, Self>
where
F: 'wrap + for<'temp> FnOnce(&'temp mut I) -> ErasedEffective<'temp, U, Self, &'ctx ()>,
I: Ss + 'wrap,
U: Ss,
F: Ss,
'ctx: 'wrap,
{
Self::ready(input).as_ctx_map(f)
}
}
impl<T: Effect> EffectExt for T {}
pub trait EffectiveExt<'lt>: Effective<'lt> {
fn remove_ctx<'wrap, Ctx: Ss, T: Ss>(self) -> ErasedEffective<'wrap, T, Self::Effect>
where
Self: Effective<'lt, Output = (Ctx, T)>,
'lt: 'wrap,
{
self.map(|(_, value)| value)
}
fn remove_value<'wrap, Ctx: Ss, T: Ss>(self) -> ErasedEffective<'wrap, Ctx, Self::Effect>
where
Self: Effective<'lt, Output = (Ctx, T)>,
'lt: 'wrap,
{
self.map(|(ctx, _)| ctx)
}
fn map<'wrap, U, F>(self, f: F) -> ErasedEffective<'wrap, U, Self::Effect>
where
F: 'wrap + FnOnce(Self::Output) -> U,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|v| ((), ControlFlow::<_, Never>::Break(f(v))),
|_, _| unreachable!(),
|_, _: Never| unreachable!(),
|_, _: &mut Never| unreachable!(),
|_, _, _: Never| unreachable!(),
|_, _, v| v,
)
}
fn then<'wrap, U, F>(self, f: F) -> ErasedEffective<'wrap, U, Self::Effect>
where
F: 'wrap + FnOnce(Self::Output) -> ErasedEffective<'wrap, U, Self::Effect>,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|v| ((), ControlFlow::Continue(v)),
|_, v| f(v).cast(),
|_, v| ControlFlow::<_, Never>::Break(v),
|_, _| unreachable!(),
|_, _, _: Never| unreachable!(),
|_, _, v| v,
)
}
fn or_else<'wrap, U, F>(self, f: F) -> ErasedEffective<'wrap, Option<U>, Self::Effect>
where
Self: Effective<'lt, Output = Option<U>>,
F: 'wrap + FnOnce() -> ErasedEffective<'wrap, Option<U>, Self::Effect>,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|v| {
(
(),
match v {
Some(v) => ControlFlow::Break(Some(v)),
None => ControlFlow::Continue(()),
},
)
},
|_, _| f().cast(),
|_, v| ControlFlow::<_, Never>::Break(v),
|_, _| unreachable!(),
|_, _, _: Never| unreachable!(),
|_, _, v| v,
)
}
#[allow(clippy::wrong_self_convention)]
#[inline(always)]
fn as_ctx<'ctx, 'wrap, U, F>(
self,
f: F,
) -> ErasedEffective<'wrap, (Self::Output, U), Self::Effect>
where
F: 'wrap
+ for<'temp> FnOnce(
&'temp mut Self::Output,
) -> ErasedEffective<'temp, U, Self::Effect, &'ctx ()>,
U: Ss,
F: Ss,
'ctx: 'lt,
'lt: 'wrap,
{
self.r#do(
|ctx| (ctx, ControlFlow::Continue(())),
#[inline(always)]
|ctx, _| f(ctx).cast(),
|_, v| ControlFlow::<_, Never>::Break(v),
|_, _| unreachable!(),
|_, _, _: Never| unreachable!(),
|ctx, _, v| (ctx, v),
)
}
#[allow(clippy::wrong_self_convention)]
fn as_ctx_map<'ctx, 'wrap, U, F>(self, f: F) -> ErasedEffective<'wrap, U, Self::Effect>
where
F: 'wrap
+ for<'temp> FnOnce(
&'temp mut Self::Output,
) -> ErasedEffective<'temp, U, Self::Effect, &'ctx ()>,
'ctx: 'lt,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|ctx| (ctx, ControlFlow::Continue(())),
|ctx, _| f(ctx).cast(),
|_, v| ControlFlow::<_, Never>::Break(v),
|_, _| unreachable!(),
|_, _, _: Never| unreachable!(),
|_, _, v| v,
)
}
#[allow(clippy::wrong_self_convention)]
fn as_ctx_or_else<'ctx, 'wrap, Ctx, U, F>(
self,
f: F,
) -> ErasedEffective<'wrap, (Ctx, Option<U>), Self::Effect>
where
Self: Effective<'lt, Output = (Ctx, Option<U>)>,
F: 'wrap
+ for<'temp> FnOnce(
&'temp mut Ctx,
)
-> ErasedEffective<'temp, Option<U>, Self::Effect, &'ctx ()>,
'ctx: 'lt,
Ctx: Ss,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|(ctx, v)| {
(
ctx,
match v {
Some(v) => ControlFlow::Break(Some(v)),
None => ControlFlow::Continue(()),
},
)
},
|ctx, ()| f(ctx).cast(),
|_, v| ControlFlow::<_, Never>::Break(v),
|_, _| unreachable!(),
|_, _, _: Never| unreachable!(),
|ctx, _, v| (ctx, v),
)
}
fn or_else_update<'ctx, 'wrap, Ctx, U, F>(
self,
f: F,
) -> ErasedEffective<'wrap, (Ctx, U), Self::Effect>
where
Self: Effective<'lt, Output = (Ctx, U)>,
F: 'wrap
+ for<'temp> FnOnce(
&'temp mut Ctx,
U::Short,
) -> ErasedEffective<'temp, U, Self::Effect, &'ctx ()>,
U: ShortCircuit,
U::Short: Ss,
Ctx: Ss,
U: Ss,
F: Ss,
'ctx: 'lt,
'lt: 'wrap,
{
self.r#do(
|(ctx, v)| {
(
ctx,
// The inverting of the control flow is because on breaks we want to run the
// function not skip it.
match U::branch(v) {
ControlFlow::Continue(v) => ControlFlow::Break(U::from_output(v)),
ControlFlow::Break(v) => ControlFlow::Continue(v),
},
)
},
|ctx, v| f(ctx, v).cast(),
|_, v| ControlFlow::<_, Never>::Break(v),
|_, _| unreachable!(),
|_, _, _: Never| unreachable!(),
|ctx, _, v| (ctx, v),
)
}
#[allow(clippy::wrong_self_convention)]
fn as_ctx_or_else_map<'ctx, 'wrap, Ctx, U, F>(
self,
f: F,
) -> ErasedEffective<'wrap, Option<U>, Self::Effect>
where
Self: Effective<'lt, Output = (Ctx, Option<U>)>,
F: 'wrap
+ for<'temp> FnOnce(
&'temp mut Ctx,
)
-> ErasedEffective<'temp, Option<U>, Self::Effect, &'ctx ()>,
'ctx: 'lt,
Ctx: Ss,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|(ctx, v)| {
(
ctx,
match v {
Some(v) => ControlFlow::Break(Some(v)),
None => ControlFlow::Continue(()),
},
)
},
|ctx, ()| f(ctx).cast(),
|_, v| ControlFlow::<_, Never>::Break(v),
|_, _| unreachable!(),
|_, _, _: Never| unreachable!(),
|_, _, v| v,
)
}
fn repeat<'ctx, 'wrap, U, F>(
self,
mut f: F,
) -> ErasedEffective<'wrap, (Self::Output, U), Self::Effect>
where
F: 'wrap
+ for<'temp> FnMut(
&'temp mut Self::Output,
)
-> ErasedEffective<'temp, ControlFlow<U>, Self::Effect, &'ctx ()>,
'ctx: 'lt,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|ctx| (ctx, ControlFlow::Continue(())),
|_, _| <Self::Effect as Effect>::ready(()).cast(),
|_, _| ControlFlow::Continue(()),
move |ctx, _| f(ctx).cast(),
|_, _, v| v,
|ctx, _, v| (ctx, v),
)
}
fn repeat_map<'ctx, 'wrap, U, F>(self, mut f: F) -> ErasedEffective<'wrap, U, Self::Effect>
where
F: 'wrap
+ for<'temp> FnMut(
&'temp mut Self::Output,
)
-> ErasedEffective<'temp, ControlFlow<U>, Self::Effect, &'ctx ()>,
'ctx: 'lt,
U: Ss,
F: Ss,
'lt: 'wrap,
{
self.r#do(
|ctx| (ctx, ControlFlow::Continue(())),
|_, _| <Self::Effect as Effect>::ready(()).cast(),
|_, _| ControlFlow::Continue(()),
move |ctx, _| f(ctx).cast(),
|_, _, v| v,
|_, _, v| v,
)
}
}
impl<'lt, T: Effective<'lt>> EffectiveExt<'lt> for T {}
pub trait Effective<'lt>: Sized + Ss + 'lt {
fn cast<'wrap, B>(self) -> ErasedEffective<'wrap, Self::Output, Self::Effect, B>
where
'lt: 'wrap;
/// The effect the effective belongs to.
type Effect: Effect;
/// The type of the effective's output value.
type Output: Ss + 'lt;
/// Future that resolves to the same value as the effective.
type IntoFuture: Ss + Future<Output = Self::Output>;
/// Convert the effective into a general future for use in async.
fn into_future(self) -> Self::IntoFuture;
fn ready(value: Self::Output) -> Self;
fn r#do<
'ctx: 'lt,
'wrap,
Pre,
Ctx,
Owned,
First,
FirstOutput,
FirstPost,
Done,
Extra,
Repeat,
RepeatOutput,
RepeatPost,
Post,
Return,
>(
self,
pre: Pre,
first: First,
first_post: FirstPost,
repeat: Repeat,
repeat_post: RepeatPost,
post: Post,
) -> ErasedEffective<'wrap, Return, Self::Effect>
where
Pre: Ss + 'wrap + FnOnce(Self::Output) -> (Ctx, ControlFlow<Done, Owned>),
First: Ss
+ 'wrap
+ for<'temp> FnOnce(
&'temp mut Ctx,
Owned,
)
-> ErasedEffective<'temp, FirstOutput, Self::Effect, &'wrap ()>,
FirstPost:
Ss + 'wrap + for<'temp> FnOnce(&'temp mut Ctx, FirstOutput) -> ControlFlow<Done, Extra>,
Repeat: Ss
+ 'wrap
+ for<'temp> FnMut(
&'temp mut Ctx,
&'temp mut Extra,
)
-> ErasedEffective<'temp, RepeatOutput, Self::Effect, &'wrap ()>,
RepeatPost: Ss
+ 'wrap
+ for<'temp> FnMut(&'temp mut Ctx, &'temp mut Extra, RepeatOutput) -> ControlFlow<Done>,
Post: Ss + 'wrap + FnOnce(Ctx, Option<Extra>, Done) -> Return,
Return: Ss,
RepeatOutput: Ss,
FirstOutput: Ss,
Owned: Ss,
Done: Ss,
Ctx: Ss,
Extra: Ss,
'lt: 'wrap;
}
pub trait TryEffective<'lt>: Effective<'lt, Output = Result<Self::Ok, Self::Err>> {
type Ok: Ss + 'lt;
type Err: Ss + 'lt;
}
impl<'lt, T, Ok, Err> TryEffective<'lt> for T
where
T: Effective<'lt, Output = Result<Ok, Err>>,
Ok: Ss + 'lt,
Err: Ss + 'lt,
{
type Ok = Ok;
type Err = Err;
}
pub trait Join {
type Effect: Effect;
fn two<'a, T0, T1>(
effectives: (T0, T1),
) -> ErasedEffective<'a, (T0::Output, T1::Output), Self::Effect>
where
T0: Ss + 'a + Effective<'a, Effect = Self::Effect>,
T1: Ss + 'a + Effective<'a, Effect = Self::Effect>;
fn three<'a, T0, T1, T2>(
effectives: (T0, T1, T2),
) -> ErasedEffective<'a, (T0::Output, T1::Output, T2::Output), Self::Effect>
where
T0: Ss + 'a + Effective<'a, Effect = Self::Effect>,
T1: Ss + 'a + Effective<'a, Effect = Self::Effect>,
T2: Ss + 'a + Effective<'a, Effect = Self::Effect>;
}
pub trait TryJoin {
type Effect: Effect;
fn two<'a, T0, T1, F0, F1>(
cb: (F0, F1),
) -> ErasedEffective<'a, Result<(T0::Ok, T1::Ok), T0::Err>, Self::Effect>
where
T0: Ss + 'a + TryEffective<'a, Effect = Self::Effect>,
T1: Ss + 'a + TryEffective<'a, Err = T0::Err, Effect = Self::Effect>,
F0: Ss + 'a + FnOnce() -> T0,
F1: Ss + 'a + FnOnce() -> T1;
fn three<'a, T0, T1, T2, F0, F1, F2>(
cb: (F0, F1, F2),
) -> ErasedEffective<'a, Result<(T0::Ok, T1::Ok, T2::Ok), T0::Err>, Self::Effect>
where
T0: Ss + 'a + TryEffective<'a, Effect = Self::Effect>,
T1: Ss + 'a + TryEffective<'a, Err = T0::Err, Effect = Self::Effect>,
T2: Ss + 'a + TryEffective<'a, Err = T0::Err, Effect = Self::Effect>,
F0: Ss + 'a + FnOnce() -> T0,
F1: Ss + 'a + FnOnce() -> T1,
F2: Ss + 'a + FnOnce() -> T2;
}
pub fn join<'lt, E: Effect, T: Joinable<'lt, E>>(x: T) -> ErasedEffective<'lt, T::Output, E> {
x.join()
}
pub trait Joinable<'lt, E: Effect> {
type Output: Ss;
fn join(self) -> ErasedEffective<'lt, Self::Output, E>;
}
impl<'lt, E: Effect> Joinable<'lt, E> for () {
type Output = ();
fn join(self) -> ErasedEffective<'lt, (), E> {
E::ready(())
}
}
impl<'lt, E: Effect, T0> Joinable<'lt, E> for (T0,)
where
T0: Effective<'lt, Effect = E>,
{
type Output = (T0::Output,);
fn join(self) -> ErasedEffective<'lt, (T0::Output,), E> {
self.0.map(|x| (x,))
}
}
impl<'lt, E: Effect, T0, T1> Joinable<'lt, E> for (T0, T1)
where
T0: Effective<'lt, Effect = E>,
T1: Effective<'lt, Effect = E>,
{
type Output = (T0::Output, T1::Output);
fn join(self) -> ErasedEffective<'lt, Self::Output, E> {
<E as Join>::two(self)
}
}
impl<'lt, E: Effect, T0, T1, T2> Joinable<'lt, E> for (T0, T1, T2)
where
T0: Effective<'lt, Effect = E>,
T1: Effective<'lt, Effect = E>,
T2: Effective<'lt, Effect = E>,
{
type Output = (T0::Output, T1::Output, T2::Output);
fn join(self) -> ErasedEffective<'lt, Self::Output, E> {
<E as Join>::three(self)
}
}
pub fn try_join<'lt, E: Effect, T: TryJoinable<'lt, E>>(
x: T,
) -> ErasedEffective<'lt, Result<T::Ok, T::Err>, E> {
x.join()
}
pub trait TryJoinable<'lt, E: Effect> {
type Ok: Ss;
type Err: Ss;
fn join(self) -> ErasedEffective<'lt, Result<Self::Ok, Self::Err>, E>;
}
impl<'lt, E: Effect> TryJoinable<'lt, E> for () {
fn join(self) -> ErasedEffective<'lt, Result<Self::Ok, Self::Err>, E> {
E::ready(Ok(()))
}
type Ok = ();
type Err = Never;
}
impl<'lt, E: Effect, F0, T0> TryJoinable<'lt, E> for (F0,)
where
F0: FnOnce() -> T0,
T0: TryEffective<'lt, Effect = E>,
{
fn join(self) -> ErasedEffective<'lt, Result<Self::Ok, Self::Err>, E> {
self.0().map(|x| x.map(|x| (x,)))
}
type Ok = (T0::Ok,);
type Err = T0::Err;
}
impl<'lt, E: Effect, F0, F1, T0, T1> TryJoinable<'lt, E> for (F0, F1)
where
F0: FnOnce() -> T0 + Ss + 'lt,
F1: FnOnce() -> T1 + Ss + 'lt,
T0: TryEffective<'lt, Effect = E>,
T1: TryEffective<'lt, Err = T0::Err, Effect = E>,
{
fn join(self) -> ErasedEffective<'lt, Result<Self::Ok, Self::Err>, E> {
<E as TryJoin>::two(self)
}
type Ok = (T0::Ok, T1::Ok);
type Err = T0::Err;
}
impl<'lt, E: Effect, F0, F1, F2, T0, T1, T2> TryJoinable<'lt, E> for (F0, F1, F2)
where
F0: FnOnce() -> T0 + Ss + 'lt,
F1: FnOnce() -> T1 + Ss + 'lt,
F2: FnOnce() -> T2 + Ss + 'lt,
T0: TryEffective<'lt, Effect = E>,
T1: TryEffective<'lt, Err = T0::Err, Effect = E>,
T2: TryEffective<'lt, Err = T0::Err, Effect = E>,
{
fn join(self) -> ErasedEffective<'lt, Result<Self::Ok, Self::Err>, E> {
<E as TryJoin>::three(self)
}
type Ok = (T0::Ok, T1::Ok, T2::Ok);
type Err = T0::Err;
}