From 445fa0016593f69a22fe8c8a9919980f33ee016c Mon Sep 17 00:00:00 2001 From: timofey Date: Sat, 11 Mar 2023 04:41:52 +0000 Subject: [PATCH] lifetimes + FutureClass --- Cargo.toml | 1 + src/copy_func.rs | 168 ++++----- src/func.rs | 478 +++----------------------- src/func/classes.rs | 2 + src/func/classes/futureclass.rs | 83 +++++ src/{ => func/classes}/optionclass.rs | 47 ++- src/func/test_suite.rs | 88 +++++ src/func/tests.rs | 352 +++++++++++++++++++ src/lib.rs | 3 +- 9 files changed, 688 insertions(+), 534 deletions(-) create mode 100644 src/func/classes.rs create mode 100644 src/func/classes/futureclass.rs rename src/{ => func/classes}/optionclass.rs (58%) create mode 100644 src/func/test_suite.rs create mode 100644 src/func/tests.rs diff --git a/Cargo.toml b/Cargo.toml index e801146..a7aac59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +futures = "0.3.26" diff --git a/src/copy_func.rs b/src/copy_func.rs index b645cf0..a053753 100644 --- a/src/copy_func.rs +++ b/src/copy_func.rs @@ -1,141 +1,147 @@ use super::func::*; pub trait CopyWeakFunctor { - type CF; + type CF<'a, A: Copy>; } pub trait CopyFunctor: CopyWeakFunctor { - fn copy_fmap B>(f: F, fa: Self::CF) -> Self::CF; + fn copy_fmap<'a, A: 'a + Copy, B: 'a + Copy, F: 'a + Fn(A) -> B>( + f: F, + fa: Self::CF<'a, A>, + ) -> Self::CF<'a, B>; - fn copy_replace(fa: Self::CF, b: B) -> Self::CF { - Self::copy_fmap(|_| b, fa) + fn copy_replace<'a, A: 'a + Copy, B: 'a + Copy>(fa: Self::CF<'a, A>, b: B) -> Self::CF<'a, B> { + Self::copy_fmap(move |_| b, fa) } - fn copy_void(fa: Self::CF) -> Self::CF<()> { + fn copy_void<'a, A: 'a + Copy>(fa: Self::CF<'a, A>) -> Self::CF<'a, ()> { Self::copy_replace(fa, ()) } } impl CopyWeakFunctor for T { - type CF = T::F; + type CF<'a, A: Copy> = T::F<'a, A>; } impl CopyFunctor for T { - fn copy_fmap B>(f: F, fa: Self::CF) -> Self::CF { + fn copy_fmap<'a, A: 'a + Copy, B: 'a + Copy, F: 'a + Fn(A) -> B>( + f: F, + fa: Self::CF<'a, A>, + ) -> Self::CF<'a, B> { Self::fmap(f, fa) } - fn copy_replace(fa: Self::CF, b: B) -> Self::CF { + fn copy_replace<'a, A: 'a + Copy, B: 'a + Copy>(fa: Self::CF<'a, A>, b: B) -> Self::CF<'a, B> { Self::replace(fa, b) } - fn copy_void(fa: Self::CF) -> Self::CF<()> { + fn copy_void<'a, A: 'a + Copy>(fa: Self::CF<'a, A>) -> Self::CF<'a, ()> { Self::void(fa) } } pub trait CopyApplicativeSeq: CopyFunctor { - fn copy_seq B>( - ff: Self::CF, - fa: Self::CF, - ) -> Self::CF; + fn copy_seq<'a, A: 'a + Copy, B: 'a + Copy, F: 'a + Copy + Fn(A) -> B>( + ff: Self::CF<'a, F>, + fa: Self::CF<'a, A>, + ) -> Self::CF<'a, B>; - fn _copy_la2 C>( + fn _copy_la2<'a, A: 'a + Copy, B: 'a + Copy, C: 'a + Copy, F: 'a + Copy + Fn(A, B) -> C>( f: F, - fa: Self::CF, - fb: Self::CF, - ) -> Self::CF { - Self::copy_seq(Self::copy_fmap(|a| move |b| f(a, b), fa), fb) + fa: Self::CF<'a, A>, + fb: Self::CF<'a, B>, + ) -> Self::CF<'a, C> { + Self::copy_seq(Self::copy_fmap(move |a| move |b| f(a, b), fa), fb) } } impl CopyApplicativeSeq for T { - fn copy_seq B>( - ff: Self::CF, - fa: Self::CF, - ) -> Self::CF { + fn copy_seq<'a, A: 'a + Copy, B: 'a + Copy, F: 'a + Copy + Fn(A) -> B>( + ff: Self::CF<'a, F>, + fa: Self::CF<'a, A>, + ) -> Self::CF<'a, B> { Self::seq(ff, fa) } } pub trait CopyApplicativeLA2: CopyFunctor { - fn copy_la2 C>( + fn copy_la2<'a, A: 'a + Copy, B: 'a + Copy, C: 'a + Copy, F: 'a + Copy + Fn(A, B) -> C>( f: F, - fa: Self::CF, - fb: Self::CF, - ) -> Self::CF; + fa: Self::CF<'a, A>, + fb: Self::CF<'a, B>, + ) -> Self::CF<'a, C>; - fn _copy_seq B>( - ff: Self::CF, - fa: Self::CF, - ) -> Self::CF { + fn _copy_seq<'a, A: 'a + Copy, B: 'a + Copy, F: 'a + Copy + Fn(A) -> B>( + ff: Self::CF<'a, F>, + fa: Self::CF<'a, A>, + ) -> Self::CF<'a, B> { Self::copy_la2(|f, a| f(a), ff, fa) } } impl CopyApplicativeLA2 for T { - fn copy_la2 C>( + fn copy_la2<'a, A: 'a + Copy, B: 'a + Copy, C: 'a + Copy, F: 'a + Copy + Fn(A, B) -> C>( f: F, - fa: Self::CF, - fb: Self::CF, - ) -> Self::CF { + fa: Self::CF<'a, A>, + fb: Self::CF<'a, B>, + ) -> Self::CF<'a, C> { Self::la2(f, fa, fb) } } -pub trait CopyApplicative: CopyFunctor + CopyApplicativeSeq + CopyApplicativeLA2 { - fn copy_pure(a: A) -> Self::CF; +// pub trait CopyApplicative: CopyFunctor + CopyApplicativeSeq + CopyApplicativeLA2 { +// fn copy_pure(a: A) -> Self::CF; - fn copy_discard_first(fa: Self::CF, fb: Self::CF) -> Self::CF { - Self::copy_seq(Self::copy_replace(fa, |b| b), fb) - } +// fn copy_discard_first(fa: Self::CF, fb: Self::CF) -> Self::CF { +// Self::copy_seq(Self::copy_replace(fa, |b| b), fb) +// } - fn copy_discard_second(fa: Self::CF, fb: Self::CF) -> Self::CF { - Self::copy_la2(|a, _| a, fa, fb) - } -} +// fn copy_discard_second(fa: Self::CF, fb: Self::CF) -> Self::CF { +// Self::copy_la2(|a, _| a, fa, fb) +// } +// } -impl CopyApplicative for T { - fn copy_pure(a: A) -> Self::CF { - Self::pure(a) - } +// impl CopyApplicative for T { +// fn copy_pure(a: A) -> Self::CF { +// Self::pure(a) +// } - fn copy_discard_first(fa: Self::CF, fb: Self::CF) -> Self::CF { - Self::discard_first(fa, fb) - } +// fn copy_discard_first(fa: Self::CF, fb: Self::CF) -> Self::CF { +// Self::discard_first(fa, fb) +// } - fn copy_discard_second(fa: Self::CF, fb: Self::CF) -> Self::CF { - Self::discard_second(fa, fb) - } -} +// fn copy_discard_second(fa: Self::CF, fb: Self::CF) -> Self::CF { +// Self::discard_second(fa, fb) +// } +// } -pub trait CopyMonad: CopyApplicative { - fn copy_bind Self::CF>( - fa: Self::CF, - f: F, - ) -> Self::CF; +// pub trait CopyMonad: CopyApplicative { +// fn copy_bind Self::CF>( +// fa: Self::CF, +// f: F, +// ) -> Self::CF; - fn copy_join(ffa: Self::CF>) -> Self::CF - where - Self::CF: Copy, - { - // ugly - Self::copy_bind(ffa, |fa| fa) - } -} +// fn copy_join(ffa: Self::CF>) -> Self::CF +// where +// Self::CF: Copy, +// { +// // ugly +// Self::copy_bind(ffa, |fa| fa) +// } +// } -impl CopyMonad for T { - fn copy_bind Self::CF>( - fa: Self::CF, - f: F, - ) -> Self::CF { - T::bind(fa, f) - } +// impl CopyMonad for T { +// fn copy_bind Self::CF>( +// fa: Self::CF, +// f: F, +// ) -> Self::CF { +// T::bind(fa, f) +// } - fn copy_join(ffa: Self::CF>) -> Self::CF - where - Self::CF: Copy, - { - T::join(ffa) - } -} +// fn copy_join(ffa: Self::CF>) -> Self::CF +// where +// Self::CF: Copy, +// { +// T::join(ffa) +// } +// } diff --git a/src/func.rs b/src/func.rs index ea603a7..9b2b7a0 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,476 +1,76 @@ +pub mod classes; +#[cfg(test)] +pub mod tests; +#[cfg(test)] +pub mod test_suite; + pub trait WeakFunctor { - type F; + type F<'a, A>; } pub trait Functor: WeakFunctor { - fn fmap B>(f: F, fa: Self::F) -> Self::F; + fn fmap<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> B>(f: F, fa: Self::F<'a, A>) -> Self::F<'a, B>; - fn replace(fa: Self::F, b: B) -> Self::F { + fn replace<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, b: B) -> Self::F<'a, B> { Self::fmap(|_| b, fa) } - fn void(fa: Self::F) -> Self::F<()> { + fn void<'a, A: 'a>(fa: Self::F<'a, A>) -> Self::F<'a, ()> { Self::replace(fa, ()) } } pub trait ApplicativeSeq: Functor { - fn seq B>(ff: Self::F, fa: Self::F) -> Self::F; - fn _la2 C>(f: F, fa: Self::F, fb: Self::F) -> Self::F { + fn seq<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> B>( + ff: Self::F<'a, F>, + fa: Self::F<'a, A>, + ) -> Self::F<'a, B>; + fn _la2<'a, A: 'a, B: 'a, C: 'a, F: 'a + FnOnce(A, B) -> C>( + f: F, + fa: Self::F<'a, A>, + fb: Self::F<'a, B>, + ) -> Self::F<'a, C> { Self::seq(Self::fmap(|a| |b| f(a, b), fa), fb) } } pub trait ApplicativeLA2: Functor { - fn la2 C>(f: F, fa: Self::F, fb: Self::F) -> Self::F; - fn _seq B>(ff: Self::F, fa: Self::F) -> Self::F { + fn la2<'a, A: 'a, B: 'a, C: 'a, F: 'a + FnOnce(A, B) -> C>( + f: F, + fa: Self::F<'a, A>, + fb: Self::F<'a, B>, + ) -> Self::F<'a, C>; + fn _seq<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> B>( + ff: Self::F<'a, F>, + fa: Self::F<'a, A>, + ) -> Self::F<'a, B> { Self::la2(|f, a| f(a), ff, fa) } } pub trait Applicative: Functor + ApplicativeSeq + ApplicativeLA2 { - fn pure(a: A) -> Self::F; + fn pure<'a, A: 'a>(a: A) -> Self::F<'a, A>; - fn discard_first(fa: Self::F, fb: Self::F) -> Self::F { + fn discard_first<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, B> { Self::seq(Self::replace(fa, |b| b), fb) } - fn discard_second(fa: Self::F, fb: Self::F) -> Self::F { + fn discard_second<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, A> { Self::la2(|a, _| a, fa, fb) } } pub trait Monad: Applicative { - fn bind Self::F>(fa: Self::F, f: F) -> Self::F; + fn bind<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> Self::F<'a, B>>( + fa: Self::F<'a, A>, + f: F, + ) -> Self::F<'a, B>; - fn join(ffa: Self::F>) -> Self::F { + fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> + where + Self::F<'a, A>: 'a, + { Self::bind(ffa, |fa| fa) } } -#[cfg(test)] -pub mod tests { - use super::*; - use core::fmt::Debug; - use std::ops::{Add, AddAssign}; - - pub struct TestResults(usize, usize, Result<(), String>); - - pub type R = TestResults; - - impl R { - pub fn unwrap(self) { - match self { - TestResults(success, total, Err(e)) => panic!("failed:\n{success}/{total}\n{e}\n"), - _ => (), - }; - } - } - - fn eqr(name: &str, left: T, right: T) -> R { - if left == right { - TestResults(1, 1, Ok(())) - } else { - TestResults(0, 1, Err(format!("{name}: {:?} != {:?}", left, right))) - } - } - - impl Default for R { - fn default() -> Self { - Self(0, 0, Ok(())) - } - } - - impl Add for R { - type Output = R; - - fn add(self, rhs: Self) -> Self::Output { - Self( - self.0 + rhs.0, - self.1 + rhs.1, - match (self.2, rhs.2) { - (Err(le), Err(re)) => Err(le + "\n" + re.as_str()), - (e, Ok(_)) => e, - (Ok(_), e) => e, - }, - ) - } - } - - impl AddAssign for R { - fn add_assign(&mut self, rhs: R) { - self.0 += rhs.0; - self.1 += rhs.1; - match (&mut self.2, rhs.2) { - (Err(ref mut le), Err(re)) => { - *le += "\n"; - *le += re.as_str() - } - (_, Ok(_)) => {} - (lr, Err(re)) => *lr = Err(re), - } - } - } - - pub fn fmap_respects_identity T::F>(fa0: FA0) -> R - where - T::F: Debug + PartialEq, - { - eqr("identity: fmap id == id", T::fmap(|a| a, fa0()), fa0()) - } - - pub fn fmap_respects_composition< - T: Functor, - A, - B, - C, - F: Fn(B) -> C, - G: Fn(A) -> B, - FA0: Fn() -> T::F, - >( - f: F, - g: G, - fa0: FA0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "composition: fmap (f . g) == fmap f . fmap g", - T::fmap(|a| f(g(a)), fa0()), - T::fmap(f, T::fmap(g, fa0())), - ) - } - - pub fn seq_respects_identity T::F>(fa0: FA0) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "identity: pure id <*> v = v", - T::seq(T::pure(|a| a), fa0()), - fa0(), - ) - } - - pub fn seq_respects_composition< - T: Applicative, - A, - B, - C, - F: Fn(B) -> C, - G: Fn(A) -> B, - FF0: Fn() -> T::F, - FG0: Fn() -> T::F, - FA0: Fn() -> T::F, - >( - ff0: FF0, - fg0: FG0, - fa0: FA0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "composition: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)", - T::seq( - T::seq( - T::seq(T::pure(|f: F| |g: G| move |a| f(g(a))), ff0()), - fg0(), - ), - fa0(), - ), - T::seq(ff0(), T::seq(fg0(), fa0())), - ) - } - - pub fn seq_is_homomorphic A, F: Copy + Fn(A) -> B>( - f: F, - a0: A0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "homomorphism: pure f <*> pure x = pure (f x)", - T::seq(T::pure(f), T::pure(a0())), - T::pure(f(a0())), - ) - } - - pub fn seq_respects_interchange< - T: Applicative, - A, - B, - F: Fn(A) -> B, - A0: Fn() -> A, - FF0: Fn() -> T::F, - >( - ff0: FF0, - a0: A0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "interchange: u <*> pure y = pure ($ y) <*> u", - T::seq(ff0(), T::pure(a0())), - T::seq(T::pure(|f: F| f(a0())), ff0()), - ) - } - - pub fn seq_can_be_expressed_via_la2< - T: Applicative, - A, - B, - F: Fn(A) -> B, - FA0: Fn() -> T::F, - FF0: Fn() -> T::F, - >( - ff0: FF0, - fa0: FA0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "seq via la2: (<*>) = liftA2 id", - T::seq(ff0(), fa0()), - T::la2(|f, a| f(a), ff0(), fa0()), - ) - } - - pub fn fmap_can_be_expressed_via_seq< - T: Applicative, - A, - B, - F: Copy + Fn(A) -> B, - FA0: Fn() -> T::F, - >( - f: F, - fa0: FA0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "fmap via seq: fmap f x = pure f <*> x", - T::fmap(f, fa0()), - T::seq(T::pure(f), fa0()), - ) - } - - pub fn discard_can_be_expressed_via_seq_or_la2< - T: Applicative, - A, - B, - FA0: Fn() -> T::F, - FB0: Fn() -> T::F, - >( - fa0: FA0, - fb0: FB0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "discard via seq: u *> v = (id <$ u) <*> v", - T::discard_first(fa0(), fb0()), - T::seq(T::replace(fa0(), |b| b), fb0()), - ) + eqr( - "discard via la2: u <* v = liftA2 const u v", - T::discard_second(fb0(), fa0()), - T::la2(|b, _| b, fb0(), fa0()), - ) - } - - pub fn bind_respects_left_identity T::F, A0: Fn() -> A>( - f: F, - a0: A0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "left identity: pure a >>= k = k a", - T::bind(T::pure(a0()), f), - f(a0()), - ) - } - - pub fn bind_respects_right_identity T::F>(fa0: FA0) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "right identity: m >>= bind = m", - T::bind(fa0(), T::pure), - fa0(), - ) - } - - pub fn bind_is_associative< - T: Monad, - A, - B, - C, - F: Copy + Fn(B) -> T::F, - G: Copy + Fn(A) -> T::F, - FA0: Fn() -> T::F, - >( - f: F, - g: G, - fa0: FA0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - r"associativity: m >>= (\x -> k x >>= h) = (m >>= k) >>= h", - T::bind(fa0(), |a| T::bind(g(a), f)), - T::bind(T::bind(fa0(), g), f), - ) - } - - pub fn seq_can_be_expressed_via_bind< - T: Monad, - A, - B, - F: Fn(A) -> B, - FA0: Fn() -> T::F, - FF0: Fn() -> T::F, - >( - ff0: FF0, - fa0: FA0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - r"seq via bind: m1 <*> m2 = m1 >>= (\x1 -> m2 >>= (\x2 -> pure (x1 x2)))", - T::seq(ff0(), fa0()), - T::bind(ff0(), |f| T::bind(fa0(), |a| T::pure(f(a)))), - ) - } - - pub fn fmap_can_be_expressed_via_bind< - T: Monad, - A, - B, - F: Copy + Fn(A) -> B, - FA0: Fn() -> T::F, - >( - f: F, - fa0: FA0, - ) -> R - where - T::F: Debug + PartialEq, - { - eqr( - "fmap via bind: fmap f xs = xs >>= return . f", - T::fmap(f, fa0()), - T::bind(fa0(), |a| T::pure(f(a))), - ) - } -} - -#[cfg(test)] -pub mod test_suite { - use std::fmt::Debug; - - use super::tests::*; - use super::*; - - pub trait FunctorTestSuite: WeakFunctor { - fn sample() -> Vec Self::F>>; - } - - pub fn functor_follows_laws() -> R - where - T::F: Debug + PartialEq, - { - let mut res = R::default(); - for pa in T::sample::<_>() { - res += fmap_respects_identity::(|| pa(2)); - } - for pa in T::sample::<_>() { - res += fmap_respects_composition::(|x| x + 5, |x| x + 3, || pa(2)); - } - res - } - - pub fn applicative_follows_laws() -> R - where - T::F: Debug + PartialEq, - { - let mut res = functor_follows_laws::(); - for pa in T::sample::<_>() { - res += seq_respects_identity::(|| pa(2)); - } - for pa in T::sample::<_>() { - for pg in T::sample::<_>() { - for pf in T::sample::<_>() { - res += seq_respects_composition::( - || pf(|x| x + 5), - || pg(|x| x + 3), - || pa(2), - ); - } - } - } - res += seq_is_homomorphic::(|x| x + 3, || 2); - for pf in T::sample::<_>() { - res += seq_respects_interchange::(|| pf(|x| x + 3), || 2); - } - for pa in T::sample::<_>() { - for pf in T::sample::<_>() { - res += seq_can_be_expressed_via_la2::(|| pf(|x| x + 3), || pa(2)); - } - } - for pa in T::sample::<_>() { - res += fmap_can_be_expressed_via_seq::(|x| x + 3, || pa(2)); - } - for pa in T::sample::<_>() { - for pb in T::sample::<_>() { - res += discard_can_be_expressed_via_seq_or_la2::(|| pa(2), || pb(2)); - } - } - res - } - - pub fn monad_follows_laws() -> R - where - T::F: Debug + PartialEq, - { - let mut res = applicative_follows_laws::(); - for pa in T::sample::<_>() { - res += bind_respects_left_identity::(|x| pa(x + 3), || 2); - } - for pa in T::sample::<_>() { - res += bind_respects_right_identity::(|| pa(2)); - } - for pa in T::sample::<_>() { - for pg in T::sample::<_>() { - for pf in T::sample::<_>() { - res += bind_is_associative::( - |x| pf(x + 5), - |x| pg(x + 3), - || pa(2), - ); - } - } - } - for pa in T::sample::<_>() { - for pf in T::sample::<_>() { - res += seq_can_be_expressed_via_bind::( - || pf(|x| x + 3), - || pa(2), - ); - } - } - for pa in T::sample::<_>() { - res += fmap_can_be_expressed_via_bind::(|x| x + 3, || pa(2)); - } - res - } -} diff --git a/src/func/classes.rs b/src/func/classes.rs new file mode 100644 index 0000000..6f0ff5e --- /dev/null +++ b/src/func/classes.rs @@ -0,0 +1,2 @@ +pub mod optionclass; +pub mod futureclass; diff --git a/src/func/classes/futureclass.rs b/src/func/classes/futureclass.rs new file mode 100644 index 0000000..6307f41 --- /dev/null +++ b/src/func/classes/futureclass.rs @@ -0,0 +1,83 @@ +use std::{future::Future, pin::Pin}; + +// use futures::FutureExt; + +use futures::join; + +use crate::func::*; + +pub struct FutureClass; + +impl WeakFunctor for FutureClass { + type F<'a, A> = Pin>>; +} + +impl Functor for FutureClass { + fn fmap<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> B>(f: F, fa: Self::F<'a, A>) -> Self::F<'a, B> { + Box::pin(async { f(fa.await) }) + } + + fn replace<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, b: B) -> Self::F<'a, B> { + Box::pin(async { + fa.await; + b + }) + } +} + +impl ApplicativeSeq for FutureClass { + fn seq<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> B>( + ff: Self::F<'a, F>, + fa: Self::F<'a, A>, + ) -> Self::F<'a, B> { + Box::pin(async { + let (f, a) = join!(ff, fa); + f(a) + }) + } +} + +impl ApplicativeLA2 for FutureClass { + fn la2<'a, A: 'a, B: 'a, C: 'a, F: 'a + FnOnce(A, B) -> C>( + f: F, + fa: Self::F<'a, A>, + fb: Self::F<'a, B>, + ) -> Self::F<'a, C> { + Box::pin(async { + let (a, b) = join!(fa, fb); + f(a, b) + }) + } +} + +impl Applicative for FutureClass { + fn pure<'a, A: 'a>(a: A) -> Self::F<'a, A> { + Box::pin(async { a }) + } + + fn discard_first<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, B> { + Box::pin(async { + join!(fa, fb).1 + }) + } + + fn discard_second<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, A> { + Box::pin(async { + join!(fa, fb).0 + }) + } +} + +impl Monad for FutureClass { + fn bind<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> Self::F<'a, B>>(fa: Self::F<'a, A>, f: F) -> Self::F<'a, B> { + Box::pin(async{ + f(fa.await).await + }) + } + + fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> where Self::F<'a, A>: 'a { + Box::pin(async{ + ffa.await.await + }) + } +} diff --git a/src/optionclass.rs b/src/func/classes/optionclass.rs similarity index 58% rename from src/optionclass.rs rename to src/func/classes/optionclass.rs index bd49a20..59e7372 100644 --- a/src/optionclass.rs +++ b/src/func/classes/optionclass.rs @@ -3,18 +3,18 @@ use crate::func::*; pub struct OptionClass; impl WeakFunctor for OptionClass { - type F = Option; + type F<'_a, A> = Option; } impl Functor for OptionClass { - fn fmap B>(f: F, fa: Self::F) -> Self::F { + fn fmap<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> B>(f: F, fa: Self::F<'a, A>) -> Self::F<'a, B> { match fa { Some(a) => Some(f(a)), None => None, } } - fn replace(fa: Self::F, b: B) -> Self::F { + fn replace<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, b: B) -> Self::F<'a, B> { match fa { Some(_) => Some(b), None => None, @@ -23,7 +23,10 @@ impl Functor for OptionClass { } impl ApplicativeSeq for OptionClass { - fn seq B>(ff: Self::F, fa: Self::F) -> Self::F { + fn seq<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> B>( + ff: Self::F<'a, F>, + fa: Self::F<'a, A>, + ) -> Self::F<'a, B> { match (ff, fa) { (Some(f), Some(a)) => Some(f(a)), _ => None, @@ -32,7 +35,11 @@ impl ApplicativeSeq for OptionClass { } impl ApplicativeLA2 for OptionClass { - fn la2 C>(f: F, fa: Self::F, fb: Self::F) -> Self::F { + fn la2<'a, A: 'a, B: 'a, C: 'a, F: 'a + FnOnce(A, B) -> C>( + f: F, + fa: Self::F<'a, A>, + fb: Self::F<'a, B>, + ) -> Self::F<'a, C> { match (fa, fb) { (Some(a), Some(b)) => Some(f(a, b)), _ => None, @@ -41,18 +48,18 @@ impl ApplicativeLA2 for OptionClass { } impl Applicative for OptionClass { - fn pure(a: A) -> Self::F { + fn pure<'a, A: 'a>(a: A) -> Self::F<'a, A> { Some(a) } - fn discard_first(fa: Self::F, fb: Self::F) -> Self::F { + fn discard_first<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, B> { match fa { Some(_) => fb, None => None, } } - fn discard_second(fa: Self::F, fb: Self::F) -> Self::F { + fn discard_second<'a, A: 'a, B: 'a>(fa: Self::F<'a, A>, fb: Self::F<'a, B>) -> Self::F<'a, A> { match fb { Some(_) => fa, None => None, @@ -61,14 +68,20 @@ impl Applicative for OptionClass { } impl Monad for OptionClass { - fn bind Self::F>(fa: Self::F, f: F) -> Self::F { + fn bind<'a, A: 'a, B: 'a, F: 'a + FnOnce(A) -> Self::F<'a, B>>( + fa: Self::F<'a, A>, + f: F, + ) -> Self::F<'a, B> { match fa { Some(a) => f(a), None => None, } } - fn join(ffa: Self::F>) -> Self::F { + fn join<'a, A: 'a>(ffa: Self::F<'a, Self::F<'a, A>>) -> Self::F<'a, A> + where + Self::F<'a, A>: 'a, + { match ffa { Some(Some(a)) => Some(a), _ => None, @@ -78,12 +91,22 @@ impl Monad for OptionClass { #[cfg(test)] mod option_tests { - use super::test_suite; + use super::{test_suite, tests}; use super::{Functor, OptionClass as T}; + impl tests::Eqr for T { + fn eqr<'a, A: PartialEq + std::fmt::Debug>( + name: &'a str, + left: Self::F<'a, A>, + right: Self::F<'a, A>, + ) -> tests::R { + tests::eqr(name, left, right) + } + } + impl test_suite::FunctorTestSuite for T { - fn sample() -> Vec Self::F>> { + fn sample<'a, A>() -> Vec Self::F<'a, A>>> { vec![Box::new(|_| None), Box::new(|a| Some(a))] } } diff --git a/src/func/test_suite.rs b/src/func/test_suite.rs new file mode 100644 index 0000000..6d7a925 --- /dev/null +++ b/src/func/test_suite.rs @@ -0,0 +1,88 @@ +use std::rc::Rc; + +use super::{tests::*, *}; + +pub trait FunctorTestSuite: WeakFunctor + Eqr { + fn sample<'a, A>() -> Vec Self::F<'a, A>>>; +} + +pub fn functor_follows_laws() -> R { + let mut res = R::default(); + for pa in T::sample::<_>() { + res += fmap_respects_identity::(|| pa(2)); + } + for pa in T::sample::<_>() { + res += fmap_respects_composition::(|x| x + 5, |x| x + 3, || pa(2)); + } + res +} + +pub fn applicative_follows_laws() -> R { + let mut res = functor_follows_laws::(); + for pa in T::sample::<_>() { + res += seq_respects_identity::(|| pa(2)); + } + for pa in T::sample::<_>() { + for pg in T::sample::<_>() { + for pf in T::sample::<_>() { + res += seq_respects_composition::( + || pf(|x| x + 5), + || pg(|x| x + 3), + || pa(2), + ); + } + } + } + res += seq_is_homomorphic::(|x| x + 3, || 2); + for pf in T::sample::<_>() { + res += seq_respects_interchange::(|| pf(|x| x + 3), || 2); + } + for pa in T::sample::<_>() { + for pf in T::sample::<_>() { + res += seq_can_be_expressed_via_la2::(|| pf(|x| x + 3), || pa(2)); + } + } + for pa in T::sample::<_>() { + res += fmap_can_be_expressed_via_seq::(|x| x + 3, || pa(2)); + } + for pa in T::sample::<_>() { + for pb in T::sample::<_>() { + res += discard_can_be_expressed_via_seq_or_la2::(|| pa(2), || pb(2)); + } + } + res +} + +pub fn monad_follows_laws() -> R +where { + let mut res = applicative_follows_laws::(); + for pa in T::sample::<_>() { + res += bind_respects_left_identity::(move |x| pa(x + 3), || 2); + } + for pa in T::sample::<_>() { + res += bind_respects_right_identity::(move || pa(2)); + } + for pa in T::sample::<_>() { + for pg in T::sample::<_>() { + let pgrc = Rc::new(pg); + for pf in T::sample::<_>() { + let pgrci = pgrc.clone(); + let pfrc = Rc::new(pf); + res += bind_is_associative::( + move |x| pfrc(x + 5), + move |x| pgrci(x + 3), + || pa(2), + ); + } + } + } + for pf in T::sample::<_>() { + for pa in T::sample::<_>() { + seq_can_be_expressed_via_bind::(|| pf(|x| x + 3), move || pa(2)); + } + } + for pa in T::sample::<_>() { + res += fmap_can_be_expressed_via_bind::(|x| x + 3, move || pa(2)); + } + res +} diff --git a/src/func/tests.rs b/src/func/tests.rs new file mode 100644 index 0000000..4c199e6 --- /dev/null +++ b/src/func/tests.rs @@ -0,0 +1,352 @@ +use super::*; +use core::fmt::Debug; +use std::ops::{Add, AddAssign}; + +pub struct TestResults { + success: usize, + total: usize, + result: Result<(), String>, +} + +pub type R = TestResults; + +impl R { + pub fn unwrap(self) { + match self { + TestResults { + success, + total, + result: Err(e), + } => panic!("failed:\n{success}/{total}\n{e}\n"), + _ => (), + }; + } +} + +pub trait Eqr: WeakFunctor { + fn eqr<'a, A: PartialEq + Debug>( + name: &'a str, + left: Self::F<'a, A>, + right: Self::F<'a, A>, + ) -> R; +} + +pub fn eqr(name: &str, left: T, right: T) -> R { + if left == right { + TestResults { + success: 1, + total: 1, + result: Ok(()), + } + } else { + TestResults { + success: 0, + total: 1, + result: Err(format!("{name}: {:?} != {:?}", left, right)), + } + } +} + +impl Default for R { + fn default() -> Self { + Self { + success: 0, + total: 0, + result: Ok(()), + } + } +} + +impl Add for R { + type Output = R; + + fn add(self, rhs: Self) -> Self::Output { + Self { + success: self.success + rhs.success, + total: self.total + rhs.total, + result: match (self.result, rhs.result) { + (Err(le), Err(re)) => Err(le + "\n" + re.as_str()), + (e, Ok(_)) => e, + (Ok(_), e) => e, + }, + } + } +} + +impl AddAssign for R { + fn add_assign(&mut self, rhs: R) { + self.success += rhs.success; + self.total += rhs.total; + match (&mut self.result, rhs.result) { + (Err(ref mut le), Err(re)) => { + *le += "\n"; + *le += re.as_str() + } + (_, Ok(_)) => {} + (lr, Err(re)) => *lr = Err(re), + } + } +} + +pub fn fmap_respects_identity< + 'a, + T: Functor + Eqr, + A: 'a + Debug + PartialEq, + FA0: Fn() -> T::F<'a, A>, +>( + fa0: FA0, +) -> R { + T::eqr("identity: fmap id == id", T::fmap(|a| a, fa0()), fa0()) +} + +pub fn fmap_respects_composition< + 'a, + T: Functor + Eqr, + A: 'a, + B: 'a, + C: 'a + Debug + PartialEq, + F: 'a + Copy + Fn(B) -> C, + G: 'a + Copy + Fn(A) -> B, + FA0: Fn() -> T::F<'a, A>, +>( + f: F, + g: G, + fa0: FA0, +) -> R { + T::eqr( + "composition: fmap (f . g) == fmap f . fmap g", + T::fmap(move |a| f(g(a)), fa0()), + T::fmap(f, T::fmap(g, fa0())), + ) +} + +pub fn seq_respects_identity< + 'a, + T: Applicative + Eqr, + A: 'a + Debug + PartialEq, + FA0: Fn() -> T::F<'a, A>, +>( + fa0: FA0, +) -> R { + T::eqr( + "identity: pure id <*> v = v", + T::seq(T::pure(|a| a), fa0()), + fa0(), + ) +} + +pub fn seq_respects_composition< + 'a, + T: Applicative + Eqr, + A: 'a, + B: 'a, + C: 'a + Debug + PartialEq, + F: 'a + Fn(B) -> C, + G: 'a + Fn(A) -> B, + FF0: Fn() -> T::F<'a, F>, + FG0: Fn() -> T::F<'a, G>, + FA0: Fn() -> T::F<'a, A>, +>( + ff0: FF0, + fg0: FG0, + fa0: FA0, +) -> R { + T::eqr( + "composition: pure (.) <*> u <*> v <*> w = u <*> (v <*> w)", + T::seq( + T::seq( + T::seq(T::pure(|f: F| move |g: G| move |a| f(g(a))), ff0()), + fg0(), + ), + fa0(), + ), + T::seq(ff0(), T::seq(fg0(), fa0())), + ) +} + +pub fn seq_is_homomorphic< + 'a, + T: Applicative + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + A0: Fn() -> A, + F: 'a + Fn(A) -> B, +>( + f: F, + a0: A0, +) -> R { + T::eqr( + "homomorphism: pure f <*> pure x = pure (f x)", + T::pure(f(a0())), + T::seq(T::pure(f), T::pure(a0())), + ) +} + +pub fn seq_respects_interchange< + 'a, + T: Applicative + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + F: 'a + Fn(A) -> B, + A0: 'a + Fn() -> A, + FF0: Fn() -> T::F<'a, F>, +>( + ff0: FF0, + a0: A0, +) -> R { + T::eqr( + "interchange: u <*> pure y = pure ($ y) <*> u", + T::seq(ff0(), T::pure(a0())), + T::seq(T::pure(move |f: F| f(a0())), ff0()), + ) +} + +pub fn seq_can_be_expressed_via_la2< + 'a, + T: Applicative + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + F: 'a + Fn(A) -> B, + FA0: Fn() -> T::F<'a, A>, + FF0: Fn() -> T::F<'a, F>, +>( + ff0: FF0, + fa0: FA0, +) -> R { + T::eqr( + "seq via la2: (<*>) = liftA2 id", + T::seq(ff0(), fa0()), + T::la2(|f, a| f(a), ff0(), fa0()), + ) +} + +pub fn fmap_can_be_expressed_via_seq< + 'a, + T: Applicative + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + F: 'a + Copy + Fn(A) -> B, + FA0: Fn() -> T::F<'a, A>, +>( + f: F, + fa0: FA0, +) -> R { + T::eqr( + "fmap via seq: fmap f x = pure f <*> x", + T::fmap(f, fa0()), + T::seq(T::pure(f), fa0()), + ) +} + +pub fn discard_can_be_expressed_via_seq_or_la2< + 'a, + T: Applicative + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + FA0: Fn() -> T::F<'a, A>, + FB0: Fn() -> T::F<'a, B>, +>( + fa0: FA0, + fb0: FB0, +) -> R { + T::eqr( + "discard via seq: u *> v = (id <$ u) <*> v", + T::discard_first(fa0(), fb0()), + T::seq(T::replace(fa0(), |b| b), fb0()), + ) + T::eqr( + "discard via la2: u <* v = liftA2 const u v", + T::discard_second(fb0(), fa0()), + T::la2(|b, _| b, fb0(), fa0()), + ) +} + +pub fn bind_respects_left_identity< + 'a, + T: Monad + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + F: 'a + Fn(A) -> T::F<'a, B>, + A0: Fn() -> A, +>( + f: F, + a0: A0, +) -> R { + T::eqr( + "left identity: pure a >>= k = k a", + f(a0()), + T::bind(T::pure(a0()), f), + ) +} + +pub fn bind_respects_right_identity< + 'a, + T: 'a + Monad + Eqr, + A: 'a + Debug + PartialEq, + FA0: Fn() -> T::F<'a, A>, +>( + fa0: FA0, +) -> R { + T::eqr( + "right identity: m >>= bind = m", + T::bind(fa0(), T::pure), + fa0(), + ) +} + +pub fn bind_is_associative< + 'a, + T: Monad + Eqr, + A: 'a, + B: 'a, + C: 'a + Debug + PartialEq, + F: 'a + Clone + Fn(B) -> T::F<'a, C>, + G: 'a + Clone + Fn(A) -> T::F<'a, B>, + FA0: Fn() -> T::F<'a, A>, +>( + f: F, + g: G, + fa0: FA0, +) -> R { + T::eqr( + r"associativity: m >>= (\x -> k x >>= h) = (m >>= k) >>= h", + T::bind(T::bind(fa0(), g.clone()), f.clone()), + T::bind(fa0(), move |a| T::bind(g(a), f)), + ) +} + +pub fn seq_can_be_expressed_via_bind< + 'a, + T: Monad + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + F: 'a + Fn(A) -> B, + FA0: 'a + Fn() -> T::F<'a, A>, + FF0: Fn() -> T::F<'a, F>, +>( + ff0: FF0, + fa0: FA0, +) -> R { + T::eqr( + r"seq via bind: m1 <*> m2 = m1 >>= (\x1 -> m2 >>= (\x2 -> pure (x1 x2)))", + T::seq(ff0(), fa0()), + T::bind(ff0(), move |f| T::bind(fa0(), move |a| T::pure(f(a)))), + ) +} + +pub fn fmap_can_be_expressed_via_bind< + 'a, + T: Monad + Eqr, + A: 'a, + B: 'a + Debug + PartialEq, + F: 'a + Copy + Fn(A) -> B, + FA0: 'a + Fn() -> T::F<'a, A>, +>( + f: F, + fa0: FA0, +) -> R { + T::eqr( + "fmap via bind: fmap f xs = xs >>= return . f", + T::fmap(f, fa0()), + T::bind(fa0(), move |a| T::pure(f(a))), + ) +} diff --git a/src/lib.rs b/src/lib.rs index ff332e6..e255570 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ +pub mod copy_func; pub mod core; pub mod func; -pub mod optionclass; -pub mod copy_func;