Skip to main content

karpal_core/
natural.rs

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