Skip to main content

karpal_core/
natural.rs

1use crate::hkt::HKT;
2#[cfg(all(not(feature = "std"), feature = "alloc"))]
3use alloc::{vec, vec::Vec};
4
5/// Natural transformation: a mapping between two functors that preserves structure.
6///
7/// Laws:
8/// - Naturality: `fmap_G(f, transform(fa)) == transform(fmap_F(f, fa))`
9pub trait NaturalTransformation<F: HKT, G: HKT> {
10    fn transform<A>(fa: F::Of<A>) -> G::Of<A>;
11}
12
13/// Converts Option to Vec (None → [], Some(a) → [a]).
14#[cfg(any(feature = "std", feature = "alloc"))]
15pub struct OptionToVec;
16
17#[cfg(any(feature = "std", feature = "alloc"))]
18impl NaturalTransformation<crate::hkt::OptionF, crate::hkt::VecF> for OptionToVec {
19    fn transform<A>(fa: Option<A>) -> Vec<A> {
20        match fa {
21            Some(a) => vec![a],
22            None => vec![],
23        }
24    }
25}
26
27/// Converts Vec to Option by taking the first element.
28#[cfg(any(feature = "std", feature = "alloc"))]
29pub struct VecHeadToOption;
30
31#[cfg(any(feature = "std", feature = "alloc"))]
32impl NaturalTransformation<crate::hkt::VecF, crate::hkt::OptionF> for VecHeadToOption {
33    fn transform<A>(fa: Vec<A>) -> Option<A> {
34        fa.into_iter().next()
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn option_to_vec_some() {
44        assert_eq!(OptionToVec::transform(Some(42)), vec![42]);
45    }
46
47    #[test]
48    fn option_to_vec_none() {
49        assert_eq!(OptionToVec::transform(None::<i32>), Vec::<i32>::new());
50    }
51
52    #[test]
53    fn vec_head_to_option_non_empty() {
54        assert_eq!(VecHeadToOption::transform(vec![1, 2, 3]), Some(1));
55    }
56
57    #[test]
58    fn vec_head_to_option_empty() {
59        assert_eq!(VecHeadToOption::transform(Vec::<i32>::new()), None);
60    }
61}
62
63#[cfg(test)]
64mod law_tests {
65    use super::*;
66    use crate::functor::Functor;
67    use crate::hkt::{OptionF, VecF};
68    use proptest::prelude::*;
69
70    proptest! {
71        // Naturality: fmap_G(f, transform(fa)) == transform(fmap_F(f, fa))
72        #[test]
73        fn option_to_vec_naturality(x in any::<Option<i32>>()) {
74            let f = |a: i32| a.wrapping_add(1);
75            let left = VecF::fmap(OptionToVec::transform(x), f);
76            let right = OptionToVec::transform(OptionF::fmap(x, f));
77            prop_assert_eq!(left, right);
78        }
79
80        #[test]
81        fn vec_head_to_option_naturality(x in prop::collection::vec(any::<i32>(), 0..10)) {
82            let f = |a: i32| a.wrapping_add(1);
83            let left = OptionF::fmap(VecHeadToOption::transform(x.clone()), f);
84            let right = VecHeadToOption::transform(VecF::fmap(x, f));
85            prop_assert_eq!(left, right);
86        }
87    }
88}