From 0b0914bd4e829a5a072787cdf8ea2b7e00cce7fb Mon Sep 17 00:00:00 2001 From: timofey Date: Fri, 10 Mar 2023 08:41:23 +0000 Subject: [PATCH] FnOnce Monad --- src/core.rs | 0 src/func.rs | 526 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 16 +- 3 files changed, 528 insertions(+), 14 deletions(-) create mode 100644 src/core.rs create mode 100644 src/func.rs diff --git a/src/core.rs b/src/core.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/func.rs b/src/func.rs new file mode 100644 index 0000000..c605992 --- /dev/null +++ b/src/func.rs @@ -0,0 +1,526 @@ +pub trait Functor { + type F; + + fn fmap B>(f: F, fa: Self::F) -> Self::F; + + fn replace(fa: Self::F, b: B) -> Self::F { + Self::fmap(|_| b, fa) + } + + fn void(fa: Self::F) -> Self::F<()> { + Self::replace(fa, ()) + } +} + +pub trait ApplicativeSeq: Functor { + fn seq B>(ff: Self::F, fa: Self::F) -> Self::F; +} + +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 { + 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; + + fn discard_first(fa: Self::F, fb: Self::F) -> Self::F { + Self::seq(Self::replace(fa, |b| b), fb) + } + + fn discard_second(fa: Self::F, fb: Self::F) -> Self::F { + Self::la2(|a, _| a, fa, fb) + } +} + +pub trait Monad: Applicative { + fn bind Self::F>(fa: Self::F, f: F) -> Self::F; + + fn join(ffa: Self::F>) -> Self::F { + Self::bind(ffa, |fa| fa) + } +} + +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 { + use super::*; + use core::fmt::Debug; + + pub fn make_none(_: Option) -> Option { + None + } + + pub fn fmap_respects_identity T::F>(fa0: FA0) + where + T::F: Debug + PartialEq, + { + assert_eq!(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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!(T::fmap(|a| f(g(a)), fa0()), T::fmap(f, T::fmap(g, fa0()))); + } + + pub fn seq_respects_identity T::F>(fa0: FA0) + where + T::F: Debug + PartialEq, + { + assert_eq!(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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!( + 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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!(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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!( + 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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!(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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!(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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!( + T::discard_first(fa0(), fb0()), + T::seq(T::replace(fa0(), |b| b), fb0()) + ); + assert_eq!( + 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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!(T::bind(T::pure(a0()), f), f(a0())); + } + + pub fn bind_respects_right_identity T::F>(fa0: FA0) + where + T::F: Debug + PartialEq, + { + assert_eq!(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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!( + 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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!( + 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, + ) where + T::F: Debug + PartialEq, + { + assert_eq!(T::fmap(f, fa0()), T::bind(fa0(), |a| T::pure(f(a)))); + } +} + +#[cfg(test)] +mod option_tests { + use crate::func::tests; + + use super::{tests::make_none, Functor, OptionClass as T}; + + #[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 fmap_respects_identity() { + tests::fmap_respects_identity::(|| None::); + tests::fmap_respects_identity::(|| Some(1)); + } + + #[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::); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7d12d9a..055af58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,2 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod core; +pub mod func;