Skip to main content

karpal_core/
natural.rs

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