radn-rs/src/func/tests.rs
timofey dc98e38b8f
All checks were successful
buildbot/cargo fmt (1.71) Build done.
buildbot/cargo doc (1.71) Build done.
buildbot/cargo clippy (1.71) Build done.
buildbot/cargo test (1.65) Build done.
buildbot/cargo clippy (1.65) Build done.
Send everything
2023-08-11 00:14:44 +00:00

320 lines
7.2 KiB
Rust

use std::{
fmt::Debug,
ops::{Add, AddAssign},
};
use super::*;
pub struct TestResults {
success: usize,
total: usize,
result: Result<(), String>,
}
pub type R = TestResults;
impl R {
pub fn unwrap(self) {
if let Self {
success,
total,
result: Err(e),
} = self
{
panic!("failed:\n{success}/{total}\n{e}\n")
}
}
}
pub trait Eqr<'a>: WeakFunctor<'a> {
fn eqr<A: 'a + Send + PartialEq + Debug>(
name: &'a str,
left: Self::F<A>,
right: Self::F<A>,
) -> R;
}
pub fn eqr<T: PartialEq + Debug>(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(el), Err(er)) => Err(el + "\n" + er.as_str()),
(e, Ok(_)) => e,
(Ok(_), e) => e,
},
}
}
}
impl AddAssign<R> 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 el), Err(er)) => {
*el += "\n";
*el += er.as_str()
}
(_, Ok(_)) => {}
(rl, Err(er)) => *rl = Err(er),
}
}
}
pub fn fmap_respects_identity<'a, T: Functor<'a> + Eqr<'a>, A: 'a + Send + Debug + PartialEq>(
fa0: impl Fn() -> T::F<A>,
) -> R {
T::eqr("identity: fmap id == id", T::fmap(fa0(), |a| a), fa0())
}
pub fn fmap_respects_composition<
'a,
T: Functor<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send,
C: 'a + Send + Debug + PartialEq,
>(
f: impl 'a + Send + Copy + Fn(B) -> C,
g: impl 'a + Send + Copy + Fn(A) -> B,
fa0: impl Fn() -> T::F<A>,
) -> R {
T::eqr(
"composition: fmap (f . g) == fmap f . fmap g",
T::fmap(fa0(), move |a| f(g(a))),
T::fmap(T::fmap(fa0(), g), f),
)
}
pub fn seq_respects_identity<'a, T: Applicative<'a> + Eqr<'a>, A: 'a + Send + Debug + PartialEq>(
fa0: impl Fn() -> T::F<A>,
) -> R {
T::eqr(
"identity: pure id <*> v = v",
T::seq(T::pure(|a| a), fa0()),
fa0(),
)
}
pub fn seq_respects_composition<
'a,
T: Applicative<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send,
C: 'a + Send + Debug + PartialEq,
F: 'a + Send + Fn(B) -> C,
G: 'a + Send + Fn(A) -> B,
>(
ff0: impl Fn() -> T::F<F>,
fg0: impl Fn() -> T::F<G>,
fa0: impl Fn() -> T::F<A>,
) -> 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<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
>(
f: impl 'a + Send + Fn(A) -> B,
a0: impl Fn() -> A,
) -> 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<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
F: 'a + Send + Fn(A) -> B,
>(
ff0: impl Fn() -> T::F<F>,
a0: impl 'a + Send + Fn() -> A,
) -> 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<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
F: 'a + Send + Fn(A) -> B,
>(
ff0: impl Fn() -> T::F<F>,
fa0: impl Fn() -> T::F<A>,
) -> R {
T::eqr(
"seq via la2: (<*>) = liftA2 id",
T::seq(ff0(), fa0()),
T::la2(ff0(), fa0(), |f, a| f(a)),
)
}
pub fn fmap_can_be_expressed_via_seq<
'a,
T: Applicative<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
>(
f: impl 'a + Send + Copy + Fn(A) -> B,
fa0: impl Fn() -> T::F<A>,
) -> R {
T::eqr(
"fmap via seq: fmap f x = pure f <*> x",
T::fmap(fa0(), f),
T::seq(T::pure(f), fa0()),
)
}
pub fn discard_can_be_expressed_via_seq_or_la2<
'a,
T: Applicative<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
>(
fa0: impl 'a + Fn() -> T::F<A>,
fb0: impl 'a + Fn() -> T::F<B>,
) -> 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(fb0(), fa0(), |b, _| b),
)
}
pub fn bind_respects_left_identity<
'a,
T: Monad<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
>(
f: impl 'a + Send + Fn(A) -> T::F<B>,
a0: impl Fn() -> A,
) -> 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: Monad<'a> + Eqr<'a>,
A: 'a + Send + Debug + PartialEq,
>(
fa0: impl Fn() -> T::F<A>,
) -> R {
T::eqr(
"right identity: m >>= bind = m",
T::bind(fa0(), T::pure),
fa0(),
)
}
pub fn bind_is_associative<
'a,
T: Monad<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send,
C: 'a + Send + Debug + PartialEq,
>(
f: impl 'a + Send + Clone + Fn(B) -> T::F<C>,
g: impl 'a + Send + Clone + Fn(A) -> T::F<B>,
fa0: impl Fn() -> T::F<A>,
) -> 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<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
F: 'a + Send + Fn(A) -> B,
>(
ff0: impl Fn() -> T::F<F>,
fa0: impl 'a + Send + Fn() -> T::F<A>,
) -> 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<'a> + Eqr<'a>,
A: 'a + Send,
B: 'a + Send + Debug + PartialEq,
>(
f: impl 'a + Send + Copy + Fn(A) -> B,
fa0: impl Fn() -> T::F<A>,
) -> R {
T::eqr(
"fmap via bind: fmap f xs = xs >>= return . f",
T::fmap(fa0(), f),
T::bind(fa0(), move |a| T::pure(f(a))),
)
}