use crate::compose::ComposeF;
use crate::functor::Functor;
#[cfg(any(feature = "std", feature = "alloc"))]
use crate::hkt::EnvF;
use crate::hkt::{HKT, HKT2, IdentityF};
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{boxed::Box, rc::Rc};
#[cfg(feature = "std")]
use std::rc::Rc;
pub trait Adjunction<F: HKT, U: HKT> {
fn unit<A: Clone + 'static>(a: A) -> U::Of<F::Of<A>>;
fn counit<B: 'static>(fub: F::Of<U::Of<B>>) -> B;
}
pub fn left_adjunct<Adj, F, U, A, B>(f: impl Fn(F::Of<A>) -> B, a: A) -> U::Of<B>
where
F: HKT,
U: Functor,
A: Clone + 'static,
Adj: Adjunction<F, U>,
{
U::fmap(Adj::unit(a), f)
}
pub fn right_adjunct<Adj, F, U, A, B>(f: impl Fn(A) -> U::Of<B>, fa: F::Of<A>) -> B
where
F: Functor,
U: HKT,
B: 'static,
Adj: Adjunction<F, U>,
{
Adj::counit(F::fmap(fa, f))
}
pub struct IdentityAdj;
impl Adjunction<IdentityF, IdentityF> for IdentityAdj {
fn unit<A: Clone + 'static>(a: A) -> A {
a
}
fn counit<B: 'static>(b: B) -> B {
b
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub struct CurryAdj<E>(core::marker::PhantomData<E>);
#[cfg(any(feature = "std", feature = "alloc"))]
impl<E: Clone + 'static> Adjunction<EnvF<E>, crate::hkt::ReaderF<E>> for CurryAdj<E> {
fn unit<A: Clone + 'static>(a: A) -> Box<dyn Fn(E) -> (E, A)> {
Box::new(move |e| (e, a.clone()))
}
fn counit<B: 'static>(fub: (E, Box<dyn Fn(E) -> B>)) -> B {
let (e, f) = fub;
f(e)
}
}
pub fn adjunction_pure<Adj, F, U, A>(a: A) -> U::Of<F::Of<A>>
where
F: HKT,
U: HKT,
A: Clone + 'static,
Adj: Adjunction<F, U>,
{
Adj::unit(a)
}
#[allow(clippy::type_complexity)]
pub fn adjunction_join<Adj, F, U, A>(ufufa: U::Of<F::Of<U::Of<F::Of<A>>>>) -> U::Of<F::Of<A>>
where
F: HKT,
U: Functor,
A: 'static,
Adj: Adjunction<F, U>,
F::Of<A>: 'static,
U::Of<F::Of<A>>: 'static,
F::Of<U::Of<F::Of<A>>>: 'static,
{
U::fmap(ufufa, |fufa: F::Of<U::Of<F::Of<A>>>| Adj::counit(fufa))
}
#[allow(clippy::type_complexity)]
pub fn adjunction_chain<Adj, F, U, A, B>(
ufa: U::Of<F::Of<A>>,
f: impl Fn(A) -> U::Of<F::Of<B>> + 'static,
) -> U::Of<F::Of<B>>
where
F: Functor,
U: Functor,
A: 'static,
B: 'static,
Adj: Adjunction<F, U>,
F::Of<B>: 'static,
U::Of<F::Of<B>>: 'static,
F::Of<U::Of<F::Of<B>>>: 'static,
{
let mapped: U::Of<F::Of<U::Of<F::Of<B>>>> = ComposeF::<U, F>::fmap(ufa, f);
adjunction_join::<Adj, F, U, B>(mapped)
}
pub fn adjunction_extract<Adj, F, U, A>(fua: F::Of<U::Of<A>>) -> A
where
F: HKT,
U: HKT,
A: 'static,
Adj: Adjunction<F, U>,
{
Adj::counit(fua)
}
#[allow(clippy::type_complexity)]
pub fn adjunction_duplicate<Adj, F, U, A>(fua: F::Of<U::Of<A>>) -> F::Of<U::Of<F::Of<U::Of<A>>>>
where
F: Functor,
U: HKT,
A: 'static,
Adj: Adjunction<F, U>,
U::Of<A>: Clone + 'static,
{
F::fmap(fua, |ua: U::Of<A>| Adj::unit(ua))
}
pub fn adjunction_extend<Adj, F, U, A, B>(
fua: F::Of<U::Of<A>>,
f: impl Fn(F::Of<U::Of<A>>) -> B + 'static,
) -> F::Of<U::Of<B>>
where
F: Functor,
U: Functor,
A: 'static,
B: Clone + 'static,
Adj: Adjunction<F, U>,
U::Of<A>: Clone + 'static,
F::Of<U::Of<A>>: 'static,
{
let duplicated = adjunction_duplicate::<Adj, F, U, A>(fua);
ComposeF::<F, U>::fmap(duplicated, f)
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn curry_left_adjunct<E: Clone + 'static, A: Clone + 'static, B: 'static>(
f: impl Fn((E, A)) -> B + 'static,
a: A,
) -> Box<dyn Fn(E) -> B> {
crate::hkt::ReaderF::<E>::fmap(CurryAdj::<E>::unit(a), f)
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn curry_right_adjunct<E: Clone + 'static, A: 'static, B: 'static>(
f: impl Fn(A) -> Box<dyn Fn(E) -> B> + 'static,
pair: (E, A),
) -> B {
CurryAdj::<E>::counit(<EnvF<E> as Functor>::fmap(pair, f))
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn state_pure<E: Clone + 'static, A: Clone + 'static>(a: A) -> Box<dyn Fn(E) -> (E, A)> {
CurryAdj::<E>::unit(a)
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn state_fmap<E: Clone + 'static, A: 'static, B: 'static>(
f: impl Fn(A) -> B + 'static,
sa: Box<dyn Fn(E) -> (E, A)>,
) -> Box<dyn Fn(E) -> (E, B)> {
crate::hkt::ReaderF::<E>::fmap(sa, move |(e, a)| (e, f(a)))
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn state_chain<E: Clone + 'static, A: 'static, B: 'static>(
ma: Box<dyn Fn(E) -> (E, A)>,
f: impl Fn(A) -> Box<dyn Fn(E) -> (E, B)> + 'static,
) -> Box<dyn Fn(E) -> (E, B)> {
Box::new(move |e| {
let (e2, a) = ma(e);
f(a)(e2)
})
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn state_get<E: Clone + 'static>() -> Box<dyn Fn(E) -> (E, E)> {
Box::new(|e: E| {
let e2 = e.clone();
(e, e2)
})
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn state_put<E: Clone + 'static>(e: E) -> Box<dyn Fn(E) -> (E, ())> {
Box::new(move |_| (e.clone(), ()))
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn state_modify<E: Clone + 'static>(f: impl Fn(E) -> E + 'static) -> Box<dyn Fn(E) -> (E, ())> {
Box::new(move |e| (f(e), ()))
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn store_extract<E: Clone + 'static, A: 'static>(store: (E, Box<dyn Fn(E) -> A>)) -> A {
CurryAdj::<E>::counit(store)
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn store_extend<E: Clone + 'static, A: 'static, B: 'static>(
store: (E, Box<dyn Fn(E) -> A>),
f: impl Fn((E, &dyn Fn(E) -> A)) -> B + 'static,
) -> (E, Box<dyn Fn(E) -> B>) {
let pos = store.0.clone();
let peek = store.1;
let new_peek: Box<dyn Fn(E) -> B> = Box::new(move |e| f((e, peek.as_ref())));
(pos, new_peek)
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn store_peek<E: Clone + 'static, A: 'static>(pos: E, store: &(E, Box<dyn Fn(E) -> A>)) -> A {
(store.1)(pos)
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn store_pos<E: Clone, A>(store: &(E, Box<dyn Fn(E) -> A>)) -> E {
store.0.clone()
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub struct ContF<R>(core::marker::PhantomData<R>);
#[cfg(any(feature = "std", feature = "alloc"))]
impl<R: 'static> HKT for ContF<R> {
type Of<T> = Box<dyn Fn(T) -> R>;
}
#[cfg(any(feature = "std", feature = "alloc"))]
impl<R: 'static> crate::contravariant::Contravariant for ContF<R> {
fn contramap<A: 'static, B>(fa: Self::Of<A>, f: impl Fn(B) -> A + 'static) -> Self::Of<B> {
Box::new(move |b| fa(f(b)))
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub trait ContravariantAdjunction<F: HKT, G: HKT> {
fn unit<A: Clone + 'static>(a: A) -> G::Of<F::Of<A>>;
fn counit<B: Clone + 'static>(b: B) -> F::Of<G::Of<B>>;
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub struct ContAdj<R>(core::marker::PhantomData<R>);
#[cfg(any(feature = "std", feature = "alloc"))]
impl<R: 'static> ContravariantAdjunction<ContF<R>, ContF<R>> for ContAdj<R> {
fn unit<A: Clone + 'static>(a: A) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R> {
Box::new(move |k: Box<dyn Fn(A) -> R>| k(a.clone()))
}
fn counit<B: Clone + 'static>(b: B) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
Box::new(move |k: Box<dyn Fn(B) -> R>| k(b.clone()))
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[allow(clippy::type_complexity)]
pub fn cont_pure<R: 'static, A: Clone + 'static>(a: A) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R> {
ContAdj::<R>::unit(a)
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[allow(clippy::type_complexity)]
pub fn cont_fmap<R: 'static, A: 'static, B: 'static>(
f: impl Fn(A) -> B + 'static,
m: Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R>,
) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
let f_rc = Rc::new(f);
Box::new(move |k: Box<dyn Fn(B) -> R>| {
let f_inner = f_rc.clone();
let k_rc = Rc::new(k);
let k_composed: Box<dyn Fn(A) -> R> = Box::new(move |a| k_rc(f_inner(a)));
m(k_composed)
})
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[allow(clippy::type_complexity)]
pub fn cont_chain<R: 'static, A: 'static, B: 'static>(
m: Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R>,
f: impl Fn(A) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> + 'static,
) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
let f_rc = Rc::new(f);
Box::new(move |k: Box<dyn Fn(B) -> R>| {
let k_rc = Rc::new(k);
let k_inner = k_rc.clone();
let f_inner = f_rc.clone();
let inner: Box<dyn Fn(A) -> R> = Box::new(move |a| {
let cont_b = f_inner(a);
let k_ref = k_inner.clone();
let k_box: Box<dyn Fn(B) -> R> = Box::new(move |b| k_ref(b));
cont_b(k_box)
});
m(inner)
})
}
#[cfg(any(feature = "std", feature = "alloc"))]
#[allow(clippy::type_complexity)]
pub fn cont_call_cc<R: 'static, A: Clone + 'static, B: 'static>(
f: impl Fn(
Box<dyn Fn(A) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R>>,
) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R>
+ 'static,
) -> Box<dyn Fn(Box<dyn Fn(A) -> R>) -> R> {
Box::new(move |k: Box<dyn Fn(A) -> R>| {
let k_rc = Rc::new(k);
let k_for_escape = k_rc.clone();
let escape: Box<dyn Fn(A) -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R>> =
Box::new(move |a: A| -> Box<dyn Fn(Box<dyn Fn(B) -> R>) -> R> {
let k_esc = k_for_escape.clone();
Box::new(move |_: Box<dyn Fn(B) -> R>| k_esc(a.clone()))
});
let k_box: Box<dyn Fn(A) -> R> = Box::new(move |a| k_rc(a));
f(escape)(k_box)
})
}
#[cfg(any(feature = "std", feature = "alloc"))]
pub fn cont_run<R, A>(m: &dyn Fn(Box<dyn Fn(A) -> R>) -> R, k: impl Fn(A) -> R + 'static) -> R {
m(Box::new(k))
}
pub trait ProfunctorFunctor {
type Applied<P: HKT2>: HKT2;
}
pub trait ProfunctorAdjunction<F: ProfunctorFunctor, U: ProfunctorFunctor> {
fn unit<P: HKT2, A: 'static, B: 'static>(
pab: P::P<A, B>,
) -> <U::Applied<F::Applied<P>> as HKT2>::P<A, B>;
fn counit<Q: HKT2, A: 'static, B: 'static>(
fuqab: <F::Applied<U::Applied<Q>> as HKT2>::P<A, B>,
) -> Q::P<A, B>;
}
pub struct ProfunctorIdentityF;
impl ProfunctorFunctor for ProfunctorIdentityF {
type Applied<P: HKT2> = P;
}
pub struct ProfunctorIdentityAdj;
impl ProfunctorAdjunction<ProfunctorIdentityF, ProfunctorIdentityF> for ProfunctorIdentityAdj {
fn unit<P: HKT2, A: 'static, B: 'static>(pab: P::P<A, B>) -> P::P<A, B> {
pab
}
fn counit<Q: HKT2, A: 'static, B: 'static>(fuqab: Q::P<A, B>) -> Q::P<A, B> {
fuqab
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_adj_unit() {
let result = IdentityAdj::unit(42);
assert_eq!(result, 42);
}
#[test]
fn identity_adj_counit() {
let result = IdentityAdj::counit(42);
assert_eq!(result, 42);
}
#[test]
fn identity_adj_left_adjunct() {
let f = |x: i32| x + 1;
let result = left_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(f, 5);
assert_eq!(result, 6);
}
#[test]
fn identity_adj_right_adjunct() {
let f = |x: i32| x * 2;
let result = right_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(f, 5);
assert_eq!(result, 10);
}
#[test]
fn identity_adj_monad_pure() {
let result = adjunction_pure::<IdentityAdj, IdentityF, IdentityF, i32>(42);
assert_eq!(result, 42);
}
#[test]
fn identity_adj_monad_join() {
let result = adjunction_join::<IdentityAdj, IdentityF, IdentityF, i32>(42);
assert_eq!(result, 42);
}
#[test]
fn identity_adj_monad_chain() {
let result = adjunction_chain::<IdentityAdj, IdentityF, IdentityF, i32, i32>(5, |x| x + 1);
assert_eq!(result, 6);
}
#[test]
fn identity_adj_comonad_extract() {
let result = adjunction_extract::<IdentityAdj, IdentityF, IdentityF, i32>(42);
assert_eq!(result, 42);
}
#[test]
fn identity_adj_comonad_duplicate() {
let result = adjunction_duplicate::<IdentityAdj, IdentityF, IdentityF, i32>(42);
assert_eq!(result, 42);
}
#[cfg(any(feature = "std", feature = "alloc"))]
mod curry_adj_tests {
use super::*;
#[test]
fn curry_adj_unit() {
let reader = CurryAdj::<i32>::unit(42i32);
assert_eq!(reader(10), (10, 42));
assert_eq!(reader(0), (0, 42));
}
#[test]
fn curry_adj_counit() {
let env_reader: (i32, Box<dyn Fn(i32) -> String>) =
(5, Box::new(|e| format!("env={}", e)));
let result = CurryAdj::<i32>::counit(env_reader);
assert_eq!(result, "env=5");
}
#[test]
fn curry_adj_monad_pure() {
let state_fn = state_pure::<i32, String>("hello".to_string());
assert_eq!(state_fn(42), (42, "hello".to_string()));
}
#[test]
fn curry_adj_state_chain() {
let get_and_inc = state_chain(
state_get::<i32>(),
|old: i32| -> Box<dyn Fn(i32) -> (i32, i32)> { Box::new(move |e| (e + 1, old)) },
);
assert_eq!(get_and_inc(10), (11, 10));
assert_eq!(get_and_inc(0), (1, 0));
}
#[test]
fn curry_adj_state_get() {
let getter = state_get::<i32>();
assert_eq!(getter(42), (42, 42));
}
#[test]
fn curry_adj_state_put() {
let putter = state_put(99i32);
assert_eq!(putter(0), (99, ()));
assert_eq!(putter(42), (99, ()));
}
#[test]
fn curry_adj_store_extract() {
let store: (i32, Box<dyn Fn(i32) -> String>) = (5, Box::new(|e| format!("val={}", e)));
let result = store_extract(store);
assert_eq!(result, "val=5");
}
#[test]
fn curry_adj_store_extend() {
let store: (i32, Box<dyn Fn(i32) -> i32>) = (3, Box::new(|e| e * 2));
let extended = store_extend(store, |(pos, peek)| peek(pos) + 1);
assert_eq!(extended.0, 3); assert_eq!((extended.1)(3), 7); assert_eq!((extended.1)(5), 11); }
#[test]
fn curry_adj_state_monad_left_identity() {
let a = 42i32;
let f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> { Box::new(move |e| (e, x + 1)) };
let chained = state_chain(state_pure(a), f);
let direct = f(a);
for e in [0, 1, 10, -5] {
assert_eq!(chained(e), direct(e));
}
}
#[test]
fn curry_adj_state_monad_right_identity() {
let m_fn = |e: i32| (e + 1, e * 2);
let chained = state_chain(
Box::new(move |e: i32| (e + 1, e * 2)) as Box<dyn Fn(i32) -> (i32, i32)>,
|a| state_pure(a),
);
for e in [0, 1, 10, -5] {
assert_eq!(chained(e), m_fn(e));
}
}
#[test]
fn reader_fmap() {
let reader: Box<dyn Fn(i32) -> i32> = Box::new(|e| e * 2);
let mapped = crate::hkt::ReaderF::<i32>::fmap(reader, |x| x + 1);
assert_eq!(mapped(5), 11); }
#[test]
fn reader_pure() {
let reader = crate::hkt::ReaderF::<i32>::pure(42);
assert_eq!(reader(0), 42);
assert_eq!(reader(999), 42);
}
#[test]
fn reader_chain() {
let reader: Box<dyn Fn(i32) -> i32> = Box::new(|e| e + 1);
let chained = crate::hkt::ReaderF::<i32>::chain(reader, |a| {
Box::new(move |e| a * e) as Box<dyn Fn(i32) -> i32>
});
assert_eq!(chained(5), 30);
}
#[test]
fn reader_ask() {
let reader = crate::hkt::ReaderF::<String>::ask();
assert_eq!(reader("hello".to_string()), "hello".to_string());
}
#[test]
fn reader_local() {
let reader: Box<dyn Fn(i32) -> i32> = Box::new(|e| e * 2);
let localized = crate::hkt::ReaderF::<i32>::local(|e| e + 10, reader);
assert_eq!(localized(5), 30); }
#[test]
fn curry_left_adjunct_test() {
let f = |pair: (i32, i32)| pair.0 + pair.1;
let reader = curry_left_adjunct(f, 10i32);
assert_eq!(reader(5), 15); assert_eq!(reader(3), 13); }
#[test]
fn curry_right_adjunct_test() {
let f = |a: i32| -> Box<dyn Fn(i32) -> i32> { Box::new(move |e| e * a) };
let result = curry_right_adjunct(f, (3i32, 5i32));
assert_eq!(result, 15); }
#[test]
fn state_fmap_test() {
let sa: Box<dyn Fn(i32) -> (i32, i32)> = Box::new(|e| (e + 1, e * 2));
let mapped = state_fmap(|x| x + 100, sa);
assert_eq!(mapped(5), (6, 110)); }
#[test]
fn state_modify_test() {
let modifier = state_modify(|e: i32| e + 10);
assert_eq!(modifier(5), (15, ()));
}
#[test]
fn store_peek_test() {
let store: (i32, Box<dyn Fn(i32) -> String>) = (3, Box::new(|e| format!("v{}", e)));
assert_eq!(store_peek(3, &store), "v3");
assert_eq!(store_peek(7, &store), "v7");
}
#[test]
fn store_pos_test() {
let store: (i32, Box<dyn Fn(i32) -> i32>) = (42, Box::new(|e| e * 2));
assert_eq!(store_pos(&store), 42);
}
}
}
#[cfg(test)]
mod law_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn identity_adj_triangle_1(x in any::<i32>()) {
let result = right_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(
IdentityAdj::unit, x,
);
prop_assert_eq!(result, x);
}
#[test]
fn identity_adj_triangle_2(x in any::<i32>()) {
let result = left_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(
IdentityAdj::counit, x,
);
prop_assert_eq!(result, x);
}
#[test]
fn identity_adj_adjuncts_inverse(x in any::<i16>()) {
let f = |a: i16| a.wrapping_add(1);
let la = left_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(f, x);
prop_assert_eq!(la, f(x));
let g = |a: i16| a.wrapping_mul(2);
let ra = right_adjunct::<IdentityAdj, IdentityF, IdentityF, _, _>(g, x);
prop_assert_eq!(ra, g(x));
}
#[test]
fn identity_adj_monad_left_identity(x in any::<i32>()) {
let f = |a: i32| a.wrapping_add(1);
let pure_x = adjunction_pure::<IdentityAdj, IdentityF, IdentityF, i32>(x);
let result = adjunction_chain::<IdentityAdj, IdentityF, IdentityF, i32, i32>(
pure_x, f,
);
prop_assert_eq!(result, f(x));
}
#[test]
fn identity_adj_monad_right_identity(x in any::<i32>()) {
let result = adjunction_chain::<IdentityAdj, IdentityF, IdentityF, i32, i32>(
x,
adjunction_pure::<IdentityAdj, IdentityF, IdentityF, i32>,
);
prop_assert_eq!(result, x);
}
#[test]
fn identity_adj_comonad_extract_duplicate(x in any::<i32>()) {
let dup = adjunction_duplicate::<IdentityAdj, IdentityF, IdentityF, i32>(x);
let result = adjunction_extract::<IdentityAdj, IdentityF, IdentityF, i32>(dup);
prop_assert_eq!(result, x);
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
mod curry_adj_law_tests {
use super::*;
proptest! {
#[test]
fn curry_adj_triangle_counit_fmap_unit(e in -100i32..100, a in -100i32..100) {
let mapped = <EnvF<i32> as crate::functor::Functor>::fmap(
(e, a),
|x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
CurryAdj::<i32>::unit(x)
},
);
let result = CurryAdj::<i32>::counit(mapped);
prop_assert_eq!(result, (e, a));
}
#[test]
fn curry_adj_state_left_identity(a in -100i32..100, e in -100i32..100) {
let f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
Box::new(move |env| (env, x.wrapping_add(1)))
};
let chained = state_chain(state_pure(a), f);
let expected = f(a);
prop_assert_eq!(chained(e), expected(e));
}
#[test]
fn curry_adj_state_right_identity(e in -100i32..100) {
let m_fn = move |env: i32| (env.wrapping_add(1), env.wrapping_mul(2));
let chained = state_chain(
Box::new(m_fn) as Box<dyn Fn(i32) -> (i32, i32)>,
|a: i32| state_pure(a),
);
prop_assert_eq!(chained(e), m_fn(e));
}
#[test]
fn curry_adj_state_associativity(e in -100i32..100) {
let _m: Box<dyn Fn(i32) -> (i32, i32)> =
Box::new(|env| (env, 10));
let f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
Box::new(move |env| (env.wrapping_add(1), x.wrapping_add(1)))
};
let g = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
Box::new(move |env| (env, x.wrapping_mul(2)))
};
let left = state_chain(state_chain(
Box::new(|env: i32| (env, 10)) as Box<dyn Fn(i32) -> (i32, i32)>,
f,
), g);
let right = state_chain(
Box::new(|env: i32| (env, 10)) as Box<dyn Fn(i32) -> (i32, i32)>,
move |a: i32| {
let inner_f = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
Box::new(move |env| (env.wrapping_add(1), x.wrapping_add(1)))
};
let inner_g = |x: i32| -> Box<dyn Fn(i32) -> (i32, i32)> {
Box::new(move |env| (env, x.wrapping_mul(2)))
};
state_chain(inner_f(a), inner_g)
},
);
prop_assert_eq!(left(e), right(e));
}
#[test]
fn curry_adj_store_extract_law(pos in -100i32..100) {
let store: (i32, Box<dyn Fn(i32) -> i32>) =
(pos, Box::new(|e| e.wrapping_mul(2)));
let result = store_extract(store);
prop_assert_eq!(result, pos.wrapping_mul(2));
}
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
mod contravariant_adj_tests {
use super::*;
#[test]
fn cont_adj_unit() {
let k: Box<dyn Fn(i32) -> i32> = Box::new(|x| x + 1);
let m = ContAdj::<i32>::unit(42);
assert_eq!(m(k), 43);
}
#[test]
fn cont_adj_counit() {
let k: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
let m = ContAdj::<i32>::counit(10);
assert_eq!(m(k), 20);
}
#[test]
fn cont_adj_self_adjoint() {
let k: Box<dyn Fn(i32) -> String> = Box::new(|x| format!("{}", x));
let unit_result = ContAdj::<String>::unit(42);
let counit_result = ContAdj::<String>::counit(42);
let k2: Box<dyn Fn(i32) -> String> = Box::new(|x| format!("{}", x));
assert_eq!(unit_result(k), counit_result(k2));
}
#[test]
fn cont_pure_test() {
let m = cont_pure::<i32, _>(42);
assert_eq!(cont_run(&*m, |x| x + 1), 43);
}
#[test]
fn cont_fmap_test() {
let m = cont_pure::<i32, _>(10);
let mapped = cont_fmap(|x: i32| x * 3, m);
assert_eq!(cont_run(&*mapped, |x| x + 1), 31); }
#[test]
fn cont_chain_test() {
let m = cont_pure::<i32, _>(5);
let chained = cont_chain(m, |x: i32| cont_pure(x + 10));
assert_eq!(cont_run(&*chained, |x| x * 2), 30); }
#[test]
fn cont_chain_sequencing() {
let m1 = cont_pure::<i32, _>(3);
let m2 = cont_chain(m1, |x| {
let doubled = x * 2; cont_chain(cont_pure(doubled), |y| cont_pure(y + 1)) });
assert_eq!(cont_run(&*m2, |x| x), 7);
}
#[test]
fn cont_call_cc_no_escape() {
let m = cont_call_cc::<i32, i32, i32>(|_escape| cont_pure(42));
assert_eq!(cont_run(&*m, |x| x), 42);
}
#[test]
fn cont_call_cc_with_escape() {
let m = cont_call_cc::<i32, i32, i32>(|escape| {
let escaped = escape(10);
cont_chain(escaped, |_| cont_pure(999))
});
assert_eq!(cont_run(&*m, |x| x), 10);
}
#[test]
fn contf_contramap() {
use crate::contravariant::Contravariant;
let f: Box<dyn Fn(i32) -> bool> = Box::new(|x| x > 0);
let g = ContF::<bool>::contramap(f, |s: &str| s.len() as i32);
assert!(g("hello"));
assert!(!g(""));
}
}
#[cfg(any(feature = "std", feature = "alloc"))]
mod contravariant_adj_law_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn cont_monad_left_identity(a in -100i32..100) {
let f = |x: i32| -> Box<dyn Fn(Box<dyn Fn(i32) -> i32>) -> i32> {
cont_pure(x.wrapping_add(1))
};
let chained = cont_chain(cont_pure(a), f);
let expected = f(a);
prop_assert_eq!(
cont_run(&*chained, |x| x),
cont_run(&*expected, |x| x)
);
}
#[test]
fn cont_monad_right_identity(a in -100i32..100) {
let m = cont_pure::<i32, _>(a);
let chained = cont_chain(m, |x: i32| cont_pure(x));
let m2 = cont_pure::<i32, _>(a);
prop_assert_eq!(
cont_run(&*chained, |x| x),
cont_run(&*m2, |x| x)
);
}
#[test]
fn cont_functor_identity(a in -100i32..100) {
let m = cont_pure::<i32, _>(a);
let mapped = cont_fmap(|x: i32| x, m);
let m2 = cont_pure::<i32, _>(a);
prop_assert_eq!(
cont_run(&*mapped, |x| x),
cont_run(&*m2, |x| x)
);
}
#[test]
fn cont_functor_composition(a in -100i32..100) {
let f = |x: i32| x.wrapping_add(1);
let g = |x: i32| x.wrapping_mul(2);
let m1 = cont_pure::<i32, _>(a);
let left = cont_fmap(move |x| g(f(x)), m1);
let m2 = cont_pure::<i32, _>(a);
let right = cont_fmap(g, cont_fmap(f, m2));
prop_assert_eq!(
cont_run(&*left, |x| x),
cont_run(&*right, |x| x)
);
}
}
}
mod profunctor_adj_tests {
use super::*;
use crate::hkt::TupleF;
#[test]
fn profunctor_identity_adj_unit() {
let val: (i32, String) = (42, "hello".to_string());
let result = ProfunctorIdentityAdj::unit::<TupleF, i32, String>(val.clone());
assert_eq!(result, val);
}
#[test]
fn profunctor_identity_adj_counit() {
let val: (i32, String) = (42, "hello".to_string());
let result = ProfunctorIdentityAdj::counit::<TupleF, i32, String>(val.clone());
assert_eq!(result, val);
}
#[test]
fn profunctor_identity_adj_roundtrip() {
let val: (i32, i32) = (1, 2);
let result =
ProfunctorIdentityAdj::counit::<TupleF, i32, i32>(ProfunctorIdentityAdj::unit::<
TupleF,
i32,
i32,
>(val));
assert_eq!(result, (1, 2));
}
}
mod profunctor_adj_law_tests {
use super::*;
use crate::hkt::TupleF;
use proptest::prelude::*;
proptest! {
#[test]
fn profunctor_identity_adj_triangle(a in any::<i32>(), b in any::<i32>()) {
let val: (i32, i32) = (a, b);
let result = ProfunctorIdentityAdj::counit::<TupleF, i32, i32>(
ProfunctorIdentityAdj::unit::<TupleF, i32, i32>(val),
);
prop_assert_eq!(result, (a, b));
}
}
}
}