From 85df7b209250504de676166664692544d30a8359 Mon Sep 17 00:00:00 2001 From: timofey Date: Fri, 10 Mar 2023 23:00:10 +0000 Subject: [PATCH] generic tests + CopyFunctor --- src/copy_func.rs | 141 ++++++++++++ src/func.rs | 542 ++++++++++++++++++++------------------------- src/lib.rs | 2 + src/optionclass.rs | 117 ++++++++++ 4 files changed, 506 insertions(+), 296 deletions(-) create mode 100644 src/copy_func.rs create mode 100644 src/optionclass.rs 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; +} + +pub trait CopyFunctor: CopyWeakFunctor { + fn copy_fmap B>(f: F, fa: Self::CF) -> Self::CF; + + fn copy_replace(fa: Self::CF, b: B) -> Self::CF { + Self::copy_fmap(|_| b, fa) + } + + fn copy_void(fa: Self::CF) -> Self::CF<()> { + Self::copy_replace(fa, ()) + } +} + +impl CopyWeakFunctor for T { + type CF = T::F; +} + +impl CopyFunctor for T { + fn copy_fmap B>(f: F, fa: Self::CF) -> Self::CF { + Self::fmap(f, fa) + } + + fn copy_replace(fa: Self::CF, b: B) -> Self::CF { + Self::replace(fa, b) + } + + fn copy_void(fa: Self::CF) -> Self::CF<()> { + Self::void(fa) + } +} + +pub trait CopyApplicativeSeq: CopyFunctor { + fn copy_seq B>( + ff: Self::CF, + fa: Self::CF, + ) -> Self::CF; + + fn _copy_la2 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) + } +} + +impl CopyApplicativeSeq for T { + fn copy_seq B>( + ff: Self::CF, + fa: Self::CF, + ) -> Self::CF { + Self::seq(ff, fa) + } +} + +pub trait CopyApplicativeLA2: CopyFunctor { + fn copy_la2 C>( + f: F, + fa: Self::CF, + fb: Self::CF, + ) -> Self::CF; + + fn _copy_seq B>( + ff: Self::CF, + fa: Self::CF, + ) -> Self::CF { + Self::copy_la2(|f, a| f(a), ff, fa) + } +} + +impl CopyApplicativeLA2 for T { + fn copy_la2 C>( + f: F, + fa: Self::CF, + fb: Self::CF, + ) -> Self::CF { + Self::la2(f, fa, fb) + } +} + +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_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) + } + + 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) + } +} + +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) + } +} + +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) + } +} 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; +pub trait WeakFunctor { + type F; +} +pub trait Functor: WeakFunctor { fn fmap B>(f: F, fa: Self::F) -> Self::F; fn replace(fa: Self::F, b: B) -> Self::F { @@ -14,28 +16,18 @@ pub trait Functor { 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 { + 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; -} - -pub trait ApplicativeSeqOnly: ApplicativeSeq {} - -pub trait ApplicativeLA2Only: ApplicativeLA2 {} - -impl ApplicativeSeq for T { - fn seq B>(ff: Self::F, fa: Self::F) -> Self::F { + fn _seq B>(ff: Self::F, fa: Self::F) -> Self::F { Self::la2(|f, a| f(a), ff, fa) } } -impl ApplicativeLA2 for T { - fn la2 C>(f: F, fa: Self::F, fb: Self::F) -> Self::F { - Self::seq(Self::fmap(|a| |b| f(a, b), fa), fb) - } -} - pub trait Applicative: Functor + ApplicativeSeq + ApplicativeLA2 { fn pure(a: A) -> Self::F; @@ -56,80 +48,75 @@ pub trait Monad: Applicative { } } -pub struct OptionClass; - -impl Functor for OptionClass { - type F = Option; - - fn fmap B>(f: F, fa: Self::F) -> Self::F { - match fa { - Some(a) => Some(f(a)), - None => None, - } - } - - fn replace(fa: Self::F, b: B) -> Self::F { - match fa { - Some(_) => Some(b), - None => None, - } - } -} - -impl ApplicativeSeq for OptionClass { - fn seq B>(ff: Self::F, fa: Self::F) -> Self::F { - match (ff, fa) { - (Some(f), Some(a)) => Some(f(a)), - _ => None, - } - } -} - -impl ApplicativeLA2 for OptionClass { - fn la2 C>(f: F, fa: Self::F, fb: Self::F) -> Self::F { - match (fa, fb) { - (Some(a), Some(b)) => Some(f(a, b)), - _ => None, - } - } -} - -impl Applicative for OptionClass { - fn pure(a: A) -> Self::F { - Some(a) - } - - fn discard_first(fa: Self::F, fb: Self::F) -> Self::F { - match fa { - Some(_) => fb, - None => None, - } - } -} - -impl Monad for OptionClass { - fn bind Self::F>(fa: Self::F, f: F) -> Self::F { - 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(_: Option) -> Option { - 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::F>(fa0: FA0) + 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, { - 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: 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::F>(fa0: FA0) + pub fn seq_respects_identity T::F>(fa0: FA0) -> R where T::F: 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: 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 A, F: Copy + Fn(A) -> B>( f: F, a0: A0, - ) where + ) -> R + where T::F: 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: 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: 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: 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: 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::F, A0: Fn() -> A>( f: F, a0: A0, - ) where + ) -> R + where T::F: 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::F>(fa0: FA0) + pub fn bind_respects_right_identity T::F>(fa0: FA0) -> R where T::F: 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: 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: 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: 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() -> Vec Self::F>>; } - #[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() -> 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 } - #[test] - fn fmap_respects_identity() { - tests::fmap_respects_identity::(|| None::); - tests::fmap_respects_identity::(|| Some(1)); + 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 } - #[test] - fn fmap_respects_composition() { - tests::fmap_respects_composition::( - |x| x + 5, - |x| x + 3, - || None::, - ); - tests::fmap_respects_composition::(|x| x + 5, |x| x + 3, || Some(2)); - } - - #[test] - fn replace_none_b_is_none() { - assert_eq!(T::replace(None::, 1), None); - assert_eq!(T::void(None::), 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::(|| None::); - tests::seq_respects_identity::(|| Some(1)); - } - - #[test] - fn seq_respects_composition() { - tests::seq_respects_composition::( - || Some(|x| x + 5), - || Some(|x| x + 3), - || Some(2), - ); - tests::seq_respects_composition::( - || tests::make_none(Some(|x| x + 5)), - || Some(|x| x + 3), - || Some(2), - ); - tests::seq_respects_composition::( - || Some(|x| x + 5), - || tests::make_none(Some(|x| x + 3)), - || Some(2), - ); - tests::seq_respects_composition::( - || Some(|x| x + 5), - || Some(|x| x + 3), - || None::, - ); - tests::seq_respects_composition::( - || tests::make_none(Some(|x| x + 5)), - || tests::make_none(Some(|x| x + 3)), - || None::, - ); - } - - #[test] - fn seq_is_homomorphic() { - tests::seq_is_homomorphic::(|x| x + 3, || 2); - } - - #[test] - fn seq_respects_interchange() { - tests::seq_respects_interchange::(|| Some(|x| x + 3), || 2); - tests::seq_respects_interchange::(|| make_none(Some(|x| x + 3)), || 2); - } - - #[test] - fn seq_can_be_expressed_via_la2() { - tests::seq_can_be_expressed_via_la2::(|| Some(|x| x + 3), || Some(2)); - tests::seq_can_be_expressed_via_la2::( - || make_none(Some(|x| x + 3)), - || Some(2), - ); - tests::seq_can_be_expressed_via_la2::(|| Some(|x| x + 3), || None::); - tests::seq_can_be_expressed_via_la2::( - || make_none(Some(|x| x + 3)), - || None::, - ); - } - - #[test] - fn fmap_can_be_expressed_via_seq() { - tests::fmap_can_be_expressed_via_seq::(|x| x + 3, || Some(2)); - tests::fmap_can_be_expressed_via_seq::(|x| x + 3, || None::); - } - - #[test] - fn discard_can_be_expressed_via_seq_or_la2() { - tests::discard_can_be_expressed_via_seq_or_la2::(|| Some(2), || Some(3)); - tests::discard_can_be_expressed_via_seq_or_la2::(|| Some(2), || None::); - tests::discard_can_be_expressed_via_seq_or_la2::(|| None::, || Some(3)); - tests::discard_can_be_expressed_via_seq_or_la2::( - || None::, - || None::, - ); - } - - #[test] - fn bind_respects_left_identity() { - tests::bind_respects_left_identity::(|x| Some(x + 3), || 2); - tests::bind_respects_left_identity::(|_| None::, || 2); - } - - #[test] - fn bind_respects_right_identity() { - tests::bind_respects_right_identity::(|| Some(1)); - tests::bind_respects_right_identity::(|| None::); - } - - #[test] - fn bind_is_associative() { - tests::bind_is_associative::( - |x| Some(x + 5), - |x| Some(x + 3), - || Some(2), - ); - tests::bind_is_associative::( - |_| None::, - |x| Some(x + 3), - || Some(2), - ); - tests::bind_is_associative::( - |x| Some(x + 5), - |_| None::, - || Some(2), - ); - tests::bind_is_associative::( - |x| Some(x + 5), - |x| Some(x + 3), - || None::, - ); - tests::bind_is_associative::( - |_| None::, - |_| None::, - || None::, - ); - } - - #[test] - fn seq_can_be_expressed_via_bind() { - tests::seq_can_be_expressed_via_bind::(|| Some(|x| x + 3), || Some(2)); - tests::seq_can_be_expressed_via_bind::( - || Some(|x| x + 3), - || None::, - ); - tests::seq_can_be_expressed_via_bind::( - || make_none(Some(|x| x + 3)), - || Some(2), - ); - tests::seq_can_be_expressed_via_bind::( - || make_none(Some(|x| x + 3)), - || None::, - ); - } - - #[test] - fn fmap_can_be_expressed_via_bind() { - tests::fmap_can_be_expressed_via_bind::(|x| x + 3, || Some(2)); - tests::fmap_can_be_expressed_via_bind::(|x| x + 3, || None::); + 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/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 = Option; +} + +impl Functor for OptionClass { + fn fmap B>(f: F, fa: Self::F) -> Self::F { + match fa { + Some(a) => Some(f(a)), + None => None, + } + } + + fn replace(fa: Self::F, b: B) -> Self::F { + match fa { + Some(_) => Some(b), + None => None, + } + } +} + +impl ApplicativeSeq for OptionClass { + fn seq B>(ff: Self::F, fa: Self::F) -> Self::F { + match (ff, fa) { + (Some(f), Some(a)) => Some(f(a)), + _ => None, + } + } +} + +impl ApplicativeLA2 for OptionClass { + fn la2 C>(f: F, fa: Self::F, fb: Self::F) -> Self::F { + match (fa, fb) { + (Some(a), Some(b)) => Some(f(a, b)), + _ => None, + } + } +} + +impl Applicative for OptionClass { + fn pure(a: A) -> Self::F { + Some(a) + } + + fn discard_first(fa: Self::F, fb: Self::F) -> Self::F { + match fa { + Some(_) => fb, + None => None, + } + } + + fn discard_second(fa: Self::F, fb: Self::F) -> Self::F { + match fb { + Some(_) => fa, + None => None, + } + } +} + +impl Monad for OptionClass { + fn bind Self::F>(fa: Self::F, f: F) -> Self::F { + match fa { + Some(a) => f(a), + None => None, + } + } + + fn join(ffa: Self::F>) -> Self::F { + 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() -> Vec Self::F>> { + 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::, 1), None); + assert_eq!(T::void(None::), 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::().unwrap(); + } +}