#[cfg(feature = "std")]
use std::boxed::Box;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::boxed::Box;
use core::marker::PhantomData;
use karpal_core::functor::Functor;
use karpal_core::hkt::HKT;
pub struct Coyoneda<F: HKT, A, B> {
f: Box<dyn Fn(B) -> A>,
fb: F::Of<B>,
_marker: PhantomData<F>,
}
impl<F: HKT, A: 'static> Coyoneda<F, A, A> {
pub fn lift(fa: F::Of<A>) -> Self {
Coyoneda {
f: Box::new(|a| a),
fb: fa,
_marker: PhantomData,
}
}
}
impl<F: HKT, A: 'static, B: 'static> Coyoneda<F, A, B> {
pub fn fmap<C: 'static>(self, g: impl Fn(A) -> C + 'static) -> Coyoneda<F, C, B> {
let old_f = self.f;
Coyoneda {
f: Box::new(move |b| g(old_f(b))),
fb: self.fb,
_marker: PhantomData,
}
}
pub fn lower(self) -> F::Of<A>
where
F: Functor,
{
F::fmap(self.fb, self.f)
}
}
pub struct CoyonedaF<F: HKT>(PhantomData<F>);
#[cfg(test)]
mod tests {
use super::*;
use karpal_core::hkt::{OptionF, VecF};
#[test]
fn lift_lower_roundtrip_option() {
let result = Coyoneda::<OptionF, _, _>::lift(Some(42)).lower();
assert_eq!(result, Some(42));
}
#[test]
fn lift_lower_roundtrip_vec() {
let result = Coyoneda::<VecF, _, _>::lift(vec![1, 2, 3]).lower();
assert_eq!(result, vec![1, 2, 3]);
}
#[test]
fn fmap_then_lower() {
let result = Coyoneda::<OptionF, _, _>::lift(Some(2))
.fmap(|x| x * 3)
.lower();
assert_eq!(result, Some(6));
}
#[test]
fn multiple_fmaps() {
let result = Coyoneda::<OptionF, _, _>::lift(Some(1))
.fmap(|x| x + 1)
.fmap(|x| x * 10)
.fmap(|x| x + 5)
.lower();
assert_eq!(result, Some(25)); }
#[test]
fn fmap_none() {
let result = Coyoneda::<OptionF, _, _>::lift(None::<i32>)
.fmap(|x| x * 2)
.lower();
assert_eq!(result, None);
}
#[test]
fn fmap_without_functor_bound() {
let co = Coyoneda::<OptionF, _, _>::lift(Some(10));
let co2 = co.fmap(|x| x + 5);
let co3 = co2.fmap(|x| format!("value: {}", x));
let result = co3.lower();
assert_eq!(result, Some("value: 15".to_string()));
}
#[test]
fn fmap_changes_type() {
let result = Coyoneda::<OptionF, _, _>::lift(Some(42))
.fmap(|x: i32| x.to_string())
.lower();
assert_eq!(result, Some("42".to_string()));
}
}
#[cfg(test)]
mod law_tests {
use super::*;
use karpal_core::hkt::OptionF;
use proptest::prelude::*;
proptest! {
#[test]
fn coyoneda_identity(x in any::<Option<i32>>()) {
let result = Coyoneda::<OptionF, _, _>::lift(x).fmap(|a| a).lower();
prop_assert_eq!(result, x);
}
#[test]
fn coyoneda_composition(x in any::<Option<i32>>()) {
let f = |a: i32| a.wrapping_add(1);
let g = |a: i32| a.wrapping_mul(2);
let left = Coyoneda::<OptionF, _, _>::lift(x).fmap(move |a| g(f(a))).lower();
let right = Coyoneda::<OptionF, _, _>::lift(x).fmap(f).fmap(g).lower();
prop_assert_eq!(left, right);
}
}
}