diff --git a/src/copy_func.rs b/src/copy_func.rs new file mode 100644 index 0000000..8722524 --- /dev/null +++ b/src/copy_func.rs @@ -0,0 +1,141 @@ +use super::func::*; + +pub trait CopyWeakFunctor { + type CF<A: Copy>; +} + +pub trait CopyFunctor: CopyWeakFunctor { + fn copy_fmap<A: Copy, B: Copy, F: Fn(A) -> B>(f: F, fa: Self::CF<A>) -> Self::CF<B>; + + fn copy_replace<A: Copy, B: Copy>(fa: Self::CF<A>, b: B) -> Self::CF<B> { + Self::copy_fmap(|_| b, fa) + } + + fn copy_void<A: Copy>(fa: Self::CF<A>) -> Self::CF<()> { + Self::copy_replace(fa, ()) + } +} + +impl<T: WeakFunctor> CopyWeakFunctor for T { + type CF<A: Copy> = T::F<A>; +} + +impl<T: Functor> CopyFunctor for T { + fn copy_fmap<A: Copy, B: Copy, F: Fn(A) -> B>(f: F, fa: Self::CF<A>) -> Self::CF<B> { + Self::fmap(f, fa) + } + + fn copy_replace<A: Copy, B: Copy>(fa: Self::CF<A>, b: B) -> Self::CF<B> { + Self::replace(fa, b) + } + + fn copy_void<A: Copy>(fa: Self::CF<A>) -> Self::CF<()> { + Self::void(fa) + } +} + +pub trait CopyApplicativeSeq: CopyFunctor { + fn copy_seq<A: Copy, B: Copy, F: Copy + Fn(A) -> B>( + ff: Self::CF<F>, + fa: Self::CF<A>, + ) -> Self::CF<B>; + + fn _copy_la2<A: Copy, B: Copy, C: Copy, F: Copy + Fn(A, B) -> C>( + f: F, + fa: Self::CF<A>, + fb: Self::CF<B>, + ) -> Self::CF<C> { + Self::copy_seq(Self::copy_fmap(|a| move |b| f(a, b), fa), fb) + } +} + +impl<T: ApplicativeSeq> CopyApplicativeSeq for T { + fn copy_seq<A: Copy, B: Copy, F: Copy + Fn(A) -> B>( + ff: Self::CF<F>, + fa: Self::CF<A>, + ) -> Self::CF<B> { + Self::seq(ff, fa) + } +} + +pub trait CopyApplicativeLA2: CopyFunctor { + fn copy_la2<A: Copy, B: Copy, C: Copy, F: Fn(A, B) -> C>( + f: F, + fa: Self::CF<A>, + fb: Self::CF<B>, + ) -> Self::CF<C>; + + fn _copy_seq<A: Copy, B: Copy, F: Copy + Fn(A) -> B>( + ff: Self::CF<F>, + fa: Self::CF<A>, + ) -> Self::CF<B> { + Self::copy_la2(|f, a| f(a), ff, fa) + } +} + +impl<T: ApplicativeLA2> CopyApplicativeLA2 for T { + fn copy_la2<A: Copy, B: Copy, C: Copy, F: Fn(A, B) -> C>( + f: F, + fa: Self::CF<A>, + fb: Self::CF<B>, + ) -> Self::CF<C> { + Self::la2(f, fa, fb) + } +} + +pub trait CopyApplicative: CopyFunctor + CopyApplicativeSeq + CopyApplicativeLA2 { + fn copy_pure<A: Copy>(a: A) -> Self::CF<A>; + + fn copy_discard_first<A: Copy, B: Copy>(fa: Self::CF<A>, fb: Self::CF<B>) -> Self::CF<B> { + Self::copy_seq(Self::copy_replace(fa, |b| b), fb) + } + + fn copy_discard_second<A: Copy, B: Copy>(fa: Self::CF<A>, fb: Self::CF<B>) -> Self::CF<A> { + Self::copy_la2(|a, _| a, fa, fb) + } +} + +impl<T: Applicative> CopyApplicative for T { + fn copy_pure<A: Copy>(a: A) -> Self::CF<A> { + Self::pure(a) + } + + fn copy_discard_first<A: Copy, B: Copy>(fa: Self::CF<A>, fb: Self::CF<B>) -> Self::CF<B> { + Self::discard_first(fa, fb) + } + + fn copy_discard_second<A: Copy, B: Copy>(fa: Self::CF<A>, fb: Self::CF<B>) -> Self::CF<A> { + Self::discard_second(fa, fb) + } +} + +pub trait CopyMonad: CopyApplicative { + fn copy_bind<A: Copy, B: Copy, F: FnOnce(A) -> Self::CF<B>>( + fa: Self::CF<A>, + f: F, + ) -> Self::CF<B>; + + fn copy_join<A: Copy>(ffa: Self::CF<Self::CF<A>>) -> Self::CF<A> + where + Self::CF<A>: Copy, + { + // ugly + Self::copy_bind(ffa, |fa| fa) + } +} + +impl<T: Monad> CopyMonad for T { + fn copy_bind<A: Copy, B: Copy, F: FnOnce(A) -> Self::CF<B>>( + fa: Self::CF<A>, + f: F, + ) -> Self::CF<B> { + T::bind(fa, f) + } + + fn copy_join<A: Copy>(ffa: Self::CF<Self::CF<A>>) -> Self::CF<A> + where + Self::CF<A>: Copy, + { + T::join(ffa) + } +} diff --git a/src/func.rs b/src/func.rs index c605992..ea603a7 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,6 +1,8 @@ -pub trait Functor { - type F<T>; +pub trait WeakFunctor { + type F<A>; +} +pub trait Functor: WeakFunctor { fn fmap<A, B, F: FnOnce(A) -> B>(f: F, fa: Self::F<A>) -> Self::F<B>; fn replace<A, B>(fa: Self::F<A>, b: B) -> Self::F<B> { @@ -14,28 +16,18 @@ pub trait Functor { pub trait ApplicativeSeq: Functor { fn seq<A, B, F: FnOnce(A) -> B>(ff: Self::F<F>, fa: Self::F<A>) -> Self::F<B>; + fn _la2<A, B, C, F: FnOnce(A, B) -> C>(f: F, fa: Self::F<A>, fb: Self::F<B>) -> Self::F<C> { + Self::seq(Self::fmap(|a| |b| f(a, b), fa), fb) + } } pub trait ApplicativeLA2: Functor { fn la2<A, B, C, F: FnOnce(A, B) -> C>(f: F, fa: Self::F<A>, fb: Self::F<B>) -> Self::F<C>; -} - -pub trait ApplicativeSeqOnly: ApplicativeSeq {} - -pub trait ApplicativeLA2Only: ApplicativeLA2 {} - -impl<T: ApplicativeLA2Only> ApplicativeSeq for T { - fn seq<A, B, F: FnOnce(A) -> B>(ff: Self::F<F>, fa: Self::F<A>) -> Self::F<B> { + fn _seq<A, B, F: FnOnce(A) -> B>(ff: Self::F<F>, fa: Self::F<A>) -> Self::F<B> { Self::la2(|f, a| f(a), ff, fa) } } -impl<T: ApplicativeSeqOnly> ApplicativeLA2 for T { - fn la2<A, B, C, F: FnOnce(A, B) -> C>(f: F, fa: Self::F<A>, fb: Self::F<B>) -> Self::F<C> { - Self::seq(Self::fmap(|a| |b| f(a, b), fa), fb) - } -} - pub trait Applicative: Functor + ApplicativeSeq + ApplicativeLA2 { fn pure<A>(a: A) -> Self::F<A>; @@ -56,80 +48,75 @@ pub trait Monad: Applicative { } } -pub struct OptionClass; - -impl Functor for OptionClass { - type F<T> = Option<T>; - - fn fmap<A, B, F: FnOnce(A) -> B>(f: F, fa: Self::F<A>) -> Self::F<B> { - match fa { - Some(a) => Some(f(a)), - None => None, - } - } - - fn replace<A, B>(fa: Self::F<A>, b: B) -> Self::F<B> { - match fa { - Some(_) => Some(b), - None => None, - } - } -} - -impl ApplicativeSeq for OptionClass { - fn seq<A, B, F: FnOnce(A) -> B>(ff: Self::F<F>, fa: Self::F<A>) -> Self::F<B> { - match (ff, fa) { - (Some(f), Some(a)) => Some(f(a)), - _ => None, - } - } -} - -impl ApplicativeLA2 for OptionClass { - fn la2<A, B, C, F: FnOnce(A, B) -> C>(f: F, fa: Self::F<A>, fb: Self::F<B>) -> Self::F<C> { - match (fa, fb) { - (Some(a), Some(b)) => Some(f(a, b)), - _ => None, - } - } -} - -impl Applicative for OptionClass { - fn pure<A>(a: A) -> Self::F<A> { - Some(a) - } - - fn discard_first<A, B>(fa: Self::F<A>, fb: Self::F<B>) -> Self::F<B> { - match fa { - Some(_) => fb, - None => None, - } - } -} - -impl Monad for OptionClass { - fn bind<A, B, F: FnOnce(A) -> Self::F<B>>(fa: Self::F<A>, f: F) -> Self::F<B> { - match fa { - Some(a) => f(a), - None => None, - } - } -} - #[cfg(test)] -mod tests { +pub mod tests { use super::*; use core::fmt::Debug; + use std::ops::{Add, AddAssign}; - pub fn make_none<T>(_: Option<T>) -> Option<T> { - None + 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"), + _ => (), + }; + } } - pub fn fmap_respects_identity<T: Functor, A, FA0: Fn() -> T::F<A>>(fa0: FA0) + fn eqr<T: PartialEq + Debug>(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<R> 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: Functor, A, FA0: Fn() -> T::F<A>>(fa0: FA0) -> R where T::F<A>: Debug + PartialEq, { - assert_eq!(T::fmap(|a| a, fa0()), fa0()); + eqr("identity: fmap id == id", T::fmap(|a| a, fa0()), fa0()) } pub fn fmap_respects_composition< @@ -144,17 +131,26 @@ mod tests { f: F, g: G, fa0: FA0, - ) where + ) -> R + where T::F<C>: Debug + PartialEq, { - assert_eq!(T::fmap(|a| f(g(a)), fa0()), T::fmap(f, T::fmap(g, fa0()))); + 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: Applicative, A, FA0: Fn() -> T::F<A>>(fa0: FA0) + pub fn seq_respects_identity<T: Applicative, A, FA0: Fn() -> T::F<A>>(fa0: FA0) -> R where T::F<A>: Debug + PartialEq, { - assert_eq!(T::seq(T::pure(|a| a), fa0()), fa0()); + eqr( + "identity: pure id <*> v = v", + T::seq(T::pure(|a| a), fa0()), + fa0(), + ) } pub fn seq_respects_composition< @@ -171,28 +167,35 @@ mod tests { ff0: FF0, fg0: FG0, fa0: FA0, - ) where + ) -> R + where T::F<C>: Debug + PartialEq, { - assert_eq!( + 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() + fg0(), ), - fa0() + fa0(), ), - T::seq(ff0(), T::seq(fg0(), fa0())) - ); + T::seq(ff0(), T::seq(fg0(), fa0())), + ) } pub fn seq_is_homomorphic<T: Applicative, A, B, A0: Fn() -> A, F: Copy + Fn(A) -> B>( f: F, a0: A0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!(T::seq(T::pure(f), T::pure(a0())), T::pure(f(a0()))); + 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< @@ -205,13 +208,15 @@ mod tests { >( ff0: FF0, a0: A0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!( + eqr( + "interchange: u <*> pure y = pure ($ y) <*> u", T::seq(ff0(), T::pure(a0())), - T::seq(T::pure(|f: F| f(a0())), ff0()) - ); + T::seq(T::pure(|f: F| f(a0())), ff0()), + ) } pub fn seq_can_be_expressed_via_la2< @@ -224,10 +229,15 @@ mod tests { >( ff0: FF0, fa0: FA0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!(T::seq(ff0(), fa0()), T::la2(|f, a| f(a), ff0(), fa0())); + 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< @@ -239,10 +249,15 @@ mod tests { >( f: F, fa0: FA0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!(T::fmap(f, fa0()), T::seq(T::pure(f), fa0())); + 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< @@ -254,33 +269,44 @@ mod tests { >( fa0: FA0, fb0: FB0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!( + eqr( + "discard via seq: u *> v = (id <$ u) <*> v", T::discard_first(fa0(), fb0()), - T::seq(T::replace(fa0(), |b| b), fb0()) - ); - assert_eq!( + 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()) - ); + T::la2(|b, _| b, fb0(), fa0()), + ) } pub fn bind_respects_left_identity<T: Monad, A, B, F: Copy + Fn(A) -> T::F<B>, A0: Fn() -> A>( f: F, a0: A0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!(T::bind(T::pure(a0()), f), f(a0())); + eqr( + "left identity: pure a >>= k = k a", + T::bind(T::pure(a0()), f), + f(a0()), + ) } - pub fn bind_respects_right_identity<T: Monad, A, FA0: Fn() -> T::F<A>>(fa0: FA0) + pub fn bind_respects_right_identity<T: Monad, A, FA0: Fn() -> T::F<A>>(fa0: FA0) -> R where T::F<A>: Debug + PartialEq, { - assert_eq!(T::bind(fa0(), T::pure), fa0()); + eqr( + "right identity: m >>= bind = m", + T::bind(fa0(), T::pure), + fa0(), + ) } pub fn bind_is_associative< @@ -295,13 +321,15 @@ mod tests { f: F, g: G, fa0: FA0, - ) where + ) -> R + where T::F<C>: Debug + PartialEq, { - assert_eq!( + 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) - ); + T::bind(T::bind(fa0(), g), f), + ) } pub fn seq_can_be_expressed_via_bind< @@ -314,13 +342,15 @@ mod tests { >( ff0: FF0, fa0: FA0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!( + 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)))) - ); + T::bind(ff0(), |f| T::bind(fa0(), |a| T::pure(f(a)))), + ) } pub fn fmap_can_be_expressed_via_bind< @@ -332,195 +362,115 @@ mod tests { >( f: F, fa0: FA0, - ) where + ) -> R + where T::F<B>: Debug + PartialEq, { - assert_eq!(T::fmap(f, fa0()), T::bind(fa0(), |a| T::pure(f(a)))); + eqr( + "fmap via bind: fmap f xs = xs >>= return . f", + T::fmap(f, fa0()), + T::bind(fa0(), |a| T::pure(f(a))), + ) } } #[cfg(test)] -mod option_tests { - use crate::func::tests; +pub mod test_suite { + use std::fmt::Debug; - use super::{tests::make_none, Functor, OptionClass as T}; + use super::tests::*; + use super::*; - #[test] - fn fmap_f_none_is_none() { - assert_eq!(T::fmap(|_: ()| (), None), None); + pub trait FunctorTestSuite: WeakFunctor { + fn sample<A>() -> Vec<Box<dyn Fn(A) -> Self::F<A>>>; } - #[test] - fn fmap_f_some_a_is_some_f_a() { - assert_eq!(T::fmap(|x| x * x, Some(2)), Some(4)); + pub fn functor_follows_laws<T: Functor + FunctorTestSuite>() -> R + where + T::F<i32>: Debug + PartialEq, + { + let mut res = R::default(); + for pa in T::sample::<_>() { + res += fmap_respects_identity::<T, _, _>(|| pa(2)); + } + for pa in T::sample::<_>() { + res += fmap_respects_composition::<T, _, _, _, _, _, _>(|x| x + 5, |x| x + 3, || pa(2)); + } + res } - #[test] - fn fmap_respects_identity() { - tests::fmap_respects_identity::<T, _, _>(|| None::<i32>); - tests::fmap_respects_identity::<T, _, _>(|| Some(1)); + pub fn applicative_follows_laws<T: Applicative + FunctorTestSuite>() -> R + where + T::F<i32>: Debug + PartialEq, + { + let mut res = functor_follows_laws::<T>(); + for pa in T::sample::<_>() { + res += seq_respects_identity::<T, _, _>(|| pa(2)); + } + for pa in T::sample::<_>() { + for pg in T::sample::<_>() { + for pf in T::sample::<_>() { + res += seq_respects_composition::<T, _, _, _, _, _, _, _, _>( + || pf(|x| x + 5), + || pg(|x| x + 3), + || pa(2), + ); + } + } + } + res += seq_is_homomorphic::<T, _, _, _, _>(|x| x + 3, || 2); + for pf in T::sample::<_>() { + res += seq_respects_interchange::<T, _, _, _, _, _>(|| pf(|x| x + 3), || 2); + } + for pa in T::sample::<_>() { + for pf in T::sample::<_>() { + res += seq_can_be_expressed_via_la2::<T, _, _, _, _, _>(|| pf(|x| x + 3), || pa(2)); + } + } + for pa in T::sample::<_>() { + res += fmap_can_be_expressed_via_seq::<T, _, _, _, _>(|x| x + 3, || pa(2)); + } + for pa in T::sample::<_>() { + for pb in T::sample::<_>() { + res += discard_can_be_expressed_via_seq_or_la2::<T, _, _, _, _>(|| pa(2), || pb(2)); + } + } + res } - #[test] - fn fmap_respects_composition() { - tests::fmap_respects_composition::<T, _, _, _, _, _, _>( - |x| x + 5, - |x| x + 3, - || None::<i32>, - ); - tests::fmap_respects_composition::<T, _, _, _, _, _, _>(|x| x + 5, |x| x + 3, || Some(2)); - } - - #[test] - fn replace_none_b_is_none() { - assert_eq!(T::replace(None::<i32>, 1), None); - assert_eq!(T::void(None::<i32>), None); - } - - #[test] - fn replace_some_a_b_is_some_b() { - assert_eq!(T::replace(Some(1), 2), Some(2)); - assert_eq!(T::void(Some(1)), Some(())); - } - - #[test] - fn seq_respects_identity() { - tests::seq_respects_identity::<T, _, _>(|| None::<i32>); - tests::seq_respects_identity::<T, _, _>(|| Some(1)); - } - - #[test] - fn seq_respects_composition() { - tests::seq_respects_composition::<T, _, _, _, _, _, _, _, _>( - || Some(|x| x + 5), - || Some(|x| x + 3), - || Some(2), - ); - tests::seq_respects_composition::<T, _, _, _, _, _, _, _, _>( - || tests::make_none(Some(|x| x + 5)), - || Some(|x| x + 3), - || Some(2), - ); - tests::seq_respects_composition::<T, _, _, _, _, _, _, _, _>( - || Some(|x| x + 5), - || tests::make_none(Some(|x| x + 3)), - || Some(2), - ); - tests::seq_respects_composition::<T, _, _, _, _, _, _, _, _>( - || Some(|x| x + 5), - || Some(|x| x + 3), - || None::<i32>, - ); - tests::seq_respects_composition::<T, _, _, _, _, _, _, _, _>( - || tests::make_none(Some(|x| x + 5)), - || tests::make_none(Some(|x| x + 3)), - || None::<i32>, - ); - } - - #[test] - fn seq_is_homomorphic() { - tests::seq_is_homomorphic::<T, _, _, _, _>(|x| x + 3, || 2); - } - - #[test] - fn seq_respects_interchange() { - tests::seq_respects_interchange::<T, _, _, _, _, _>(|| Some(|x| x + 3), || 2); - tests::seq_respects_interchange::<T, _, _, _, _, _>(|| make_none(Some(|x| x + 3)), || 2); - } - - #[test] - fn seq_can_be_expressed_via_la2() { - tests::seq_can_be_expressed_via_la2::<T, _, _, _, _, _>(|| Some(|x| x + 3), || Some(2)); - tests::seq_can_be_expressed_via_la2::<T, _, _, _, _, _>( - || make_none(Some(|x| x + 3)), - || Some(2), - ); - tests::seq_can_be_expressed_via_la2::<T, _, _, _, _, _>(|| Some(|x| x + 3), || None::<i32>); - tests::seq_can_be_expressed_via_la2::<T, _, _, _, _, _>( - || make_none(Some(|x| x + 3)), - || None::<i32>, - ); - } - - #[test] - fn fmap_can_be_expressed_via_seq() { - tests::fmap_can_be_expressed_via_seq::<T, _, _, _, _>(|x| x + 3, || Some(2)); - tests::fmap_can_be_expressed_via_seq::<T, _, _, _, _>(|x| x + 3, || None::<i32>); - } - - #[test] - fn discard_can_be_expressed_via_seq_or_la2() { - tests::discard_can_be_expressed_via_seq_or_la2::<T, _, _, _, _>(|| Some(2), || Some(3)); - tests::discard_can_be_expressed_via_seq_or_la2::<T, _, _, _, _>(|| Some(2), || None::<i32>); - tests::discard_can_be_expressed_via_seq_or_la2::<T, _, _, _, _>(|| None::<i32>, || Some(3)); - tests::discard_can_be_expressed_via_seq_or_la2::<T, _, _, _, _>( - || None::<i32>, - || None::<i32>, - ); - } - - #[test] - fn bind_respects_left_identity() { - tests::bind_respects_left_identity::<T, _, _, _, _>(|x| Some(x + 3), || 2); - tests::bind_respects_left_identity::<T, _, _, _, _>(|_| None::<i32>, || 2); - } - - #[test] - fn bind_respects_right_identity() { - tests::bind_respects_right_identity::<T, _, _>(|| Some(1)); - tests::bind_respects_right_identity::<T, _, _>(|| None::<i32>); - } - - #[test] - fn bind_is_associative() { - tests::bind_is_associative::<T, _, _, _, _, _, _>( - |x| Some(x + 5), - |x| Some(x + 3), - || Some(2), - ); - tests::bind_is_associative::<T, _, _, _, _, _, _>( - |_| None::<i32>, - |x| Some(x + 3), - || Some(2), - ); - tests::bind_is_associative::<T, _, _, _, _, _, _>( - |x| Some(x + 5), - |_| None::<i32>, - || Some(2), - ); - tests::bind_is_associative::<T, _, _, _, _, _, _>( - |x| Some(x + 5), - |x| Some(x + 3), - || None::<i32>, - ); - tests::bind_is_associative::<T, _, _, _, _, _, _>( - |_| None::<i32>, - |_| None::<i32>, - || None::<i32>, - ); - } - - #[test] - fn seq_can_be_expressed_via_bind() { - tests::seq_can_be_expressed_via_bind::<T, _, _, _, _, _>(|| Some(|x| x + 3), || Some(2)); - tests::seq_can_be_expressed_via_bind::<T, _, _, _, _, _>( - || Some(|x| x + 3), - || None::<i32>, - ); - tests::seq_can_be_expressed_via_bind::<T, _, _, _, _, _>( - || make_none(Some(|x| x + 3)), - || Some(2), - ); - tests::seq_can_be_expressed_via_bind::<T, _, _, _, _, _>( - || make_none(Some(|x| x + 3)), - || None::<i32>, - ); - } - - #[test] - fn fmap_can_be_expressed_via_bind() { - tests::fmap_can_be_expressed_via_bind::<T, _, _, _, _>(|x| x + 3, || Some(2)); - tests::fmap_can_be_expressed_via_bind::<T, _, _, _, _>(|x| x + 3, || None::<i32>); + pub fn monad_follows_laws<T: Monad + FunctorTestSuite>() -> R + where + T::F<i32>: Debug + PartialEq, + { + let mut res = applicative_follows_laws::<T>(); + for pa in T::sample::<_>() { + res += bind_respects_left_identity::<T, _, _, _, _>(|x| pa(x + 3), || 2); + } + for pa in T::sample::<_>() { + res += bind_respects_right_identity::<T, _, _>(|| pa(2)); + } + for pa in T::sample::<_>() { + for pg in T::sample::<_>() { + for pf in T::sample::<_>() { + res += bind_is_associative::<T, _, _, _, _, _, _>( + |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::<T, _, _, _, _, _>( + || pf(|x| x + 3), + || pa(2), + ); + } + } + for pa in T::sample::<_>() { + res += fmap_can_be_expressed_via_bind::<T, _, _, _, _>(|x| x + 3, || pa(2)); + } + res } } diff --git a/src/lib.rs b/src/lib.rs index 055af58..ff332e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,4 @@ pub mod core; pub mod func; +pub mod optionclass; +pub mod copy_func; diff --git a/src/optionclass.rs b/src/optionclass.rs new file mode 100644 index 0000000..bd49a20 --- /dev/null +++ b/src/optionclass.rs @@ -0,0 +1,117 @@ +use crate::func::*; + +pub struct OptionClass; + +impl WeakFunctor for OptionClass { + type F<A> = Option<A>; +} + +impl Functor for OptionClass { + fn fmap<A, B, F: FnOnce(A) -> B>(f: F, fa: Self::F<A>) -> Self::F<B> { + match fa { + Some(a) => Some(f(a)), + None => None, + } + } + + fn replace<A, B>(fa: Self::F<A>, b: B) -> Self::F<B> { + match fa { + Some(_) => Some(b), + None => None, + } + } +} + +impl ApplicativeSeq for OptionClass { + fn seq<A, B, F: FnOnce(A) -> B>(ff: Self::F<F>, fa: Self::F<A>) -> Self::F<B> { + match (ff, fa) { + (Some(f), Some(a)) => Some(f(a)), + _ => None, + } + } +} + +impl ApplicativeLA2 for OptionClass { + fn la2<A, B, C, F: FnOnce(A, B) -> C>(f: F, fa: Self::F<A>, fb: Self::F<B>) -> Self::F<C> { + match (fa, fb) { + (Some(a), Some(b)) => Some(f(a, b)), + _ => None, + } + } +} + +impl Applicative for OptionClass { + fn pure<A>(a: A) -> Self::F<A> { + Some(a) + } + + fn discard_first<A, B>(fa: Self::F<A>, fb: Self::F<B>) -> Self::F<B> { + match fa { + Some(_) => fb, + None => None, + } + } + + fn discard_second<A, B>(fa: Self::F<A>, fb: Self::F<B>) -> Self::F<A> { + match fb { + Some(_) => fa, + None => None, + } + } +} + +impl Monad for OptionClass { + fn bind<A, B, F: FnOnce(A) -> Self::F<B>>(fa: Self::F<A>, f: F) -> Self::F<B> { + match fa { + Some(a) => f(a), + None => None, + } + } + + fn join<A>(ffa: Self::F<Self::F<A>>) -> Self::F<A> { + match ffa { + Some(Some(a)) => Some(a), + _ => None, + } + } +} + +#[cfg(test)] +mod option_tests { + use super::test_suite; + + use super::{Functor, OptionClass as T}; + + impl test_suite::FunctorTestSuite for T { + fn sample<A>() -> Vec<Box<dyn Fn(A) -> Self::F<A>>> { + vec![Box::new(|_| None), Box::new(|a| Some(a))] + } + } + + #[test] + fn fmap_f_none_is_none() { + assert_eq!(T::fmap(|_: ()| (), None), None); + } + + #[test] + fn fmap_f_some_a_is_some_f_a() { + assert_eq!(T::fmap(|x| x * x, Some(2)), Some(4)); + } + + #[test] + fn replace_none_b_is_none() { + assert_eq!(T::replace(None::<i32>, 1), None); + assert_eq!(T::void(None::<i32>), None); + } + + #[test] + fn replace_some_a_b_is_some_b() { + assert_eq!(T::replace(Some(1), 2), Some(2)); + assert_eq!(T::void(Some(1)), Some(())); + } + + #[test] + fn monad_follows_laws() { + test_suite::monad_follows_laws::<T>().unwrap(); + } +}