1use crate::hkt::{EnvF, HKT, IdentityF, OptionF, ResultF};
2#[cfg(any(feature = "std", feature = "alloc"))]
3use crate::hkt::{NonEmptyVec, NonEmptyVecF, VecF};
4#[cfg(all(not(feature = "std"), feature = "alloc"))]
5use alloc::vec::Vec;
6
7pub trait Invariant: HKT {
16 fn invmap<A, B>(fa: Self::Of<A>, f: impl Fn(A) -> B, g: impl Fn(B) -> A) -> Self::Of<B>;
17}
18
19impl Invariant for OptionF {
20 fn invmap<A, B>(fa: Option<A>, f: impl Fn(A) -> B, _g: impl Fn(B) -> A) -> Option<B> {
21 fa.map(f)
22 }
23}
24
25impl<E> Invariant for ResultF<E> {
26 fn invmap<A, B>(fa: Result<A, E>, f: impl Fn(A) -> B, _g: impl Fn(B) -> A) -> Result<B, E> {
27 fa.map(f)
28 }
29}
30
31#[cfg(any(feature = "std", feature = "alloc"))]
32impl Invariant for VecF {
33 fn invmap<A, B>(fa: Vec<A>, f: impl Fn(A) -> B, _g: impl Fn(B) -> A) -> Vec<B> {
34 fa.into_iter().map(f).collect()
35 }
36}
37
38impl Invariant for IdentityF {
39 fn invmap<A, B>(fa: A, f: impl Fn(A) -> B, _g: impl Fn(B) -> A) -> B {
40 f(fa)
41 }
42}
43
44#[cfg(any(feature = "std", feature = "alloc"))]
45impl Invariant for NonEmptyVecF {
46 fn invmap<A, B>(fa: NonEmptyVec<A>, f: impl Fn(A) -> B, _g: impl Fn(B) -> A) -> NonEmptyVec<B> {
47 NonEmptyVec::new(f(fa.head), fa.tail.into_iter().map(&f).collect())
48 }
49}
50
51impl<E> Invariant for EnvF<E> {
52 fn invmap<A, B>(fa: (E, A), f: impl Fn(A) -> B, _g: impl Fn(B) -> A) -> (E, B) {
53 (fa.0, f(fa.1))
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn option_invmap() {
63 let result = OptionF::invmap(Some(3), |x| x * 2, |x| x / 2);
64 assert_eq!(result, Some(6));
65 }
66
67 #[test]
68 fn option_invmap_none() {
69 let result = OptionF::invmap(None::<i32>, |x| x * 2, |x| x / 2);
70 assert_eq!(result, None);
71 }
72
73 #[test]
74 fn result_invmap() {
75 let result = ResultF::<&str>::invmap(Ok(5), |x| x + 1, |x| x - 1);
76 assert_eq!(result, Ok(6));
77 }
78
79 #[test]
80 fn vec_invmap() {
81 let result = VecF::invmap(vec![1, 2, 3], |x| x * 2, |x| x / 2);
82 assert_eq!(result, vec![2, 4, 6]);
83 }
84
85 #[test]
86 fn identity_invmap() {
87 let result = IdentityF::invmap(42, |x| x + 1, |x| x - 1);
88 assert_eq!(result, 43);
89 }
90
91 #[test]
92 fn nonemptyvec_invmap() {
93 let nev = NonEmptyVec::new(1, vec![2, 3]);
94 let result = NonEmptyVecF::invmap(nev, |x| x * 10, |x| x / 10);
95 assert_eq!(result, NonEmptyVec::new(10, vec![20, 30]));
96 }
97
98 #[test]
99 fn env_invmap() {
100 let result = EnvF::<&str>::invmap(("hello", 42), |x| x + 1, |x| x - 1);
101 assert_eq!(result, ("hello", 43));
102 }
103}
104
105#[cfg(test)]
106mod law_tests {
107 use super::*;
108 use proptest::prelude::*;
109
110 proptest! {
111 #[test]
113 fn option_identity(x in any::<Option<i32>>()) {
114 let result = OptionF::invmap(x, |a| a, |a| a);
115 prop_assert_eq!(result, x);
116 }
117
118 #[test]
120 fn option_composition(x in any::<Option<i16>>()) {
121 let f1 = |a: i16| a.wrapping_add(1);
122 let f2 = |a: i16| a.wrapping_sub(1);
123 let g1 = |a: i16| a.wrapping_mul(2);
124 let g2 = |a: i16| a / 2; let left = OptionF::invmap(x, |a| g1(f1(a)), |a| f2(g2(a)));
127 let right = OptionF::invmap(OptionF::invmap(x, f1, f2), g1, g2);
128 prop_assert_eq!(left, right);
129 }
130
131 #[test]
132 fn vec_identity(x in prop::collection::vec(any::<i32>(), 0..10)) {
133 let result = VecF::invmap(x.clone(), |a| a, |a| a);
134 prop_assert_eq!(result, x);
135 }
136
137 #[test]
138 fn vec_composition(x in prop::collection::vec(any::<i16>(), 0..10)) {
139 let f1 = |a: i16| a.wrapping_add(1);
140 let f2 = |a: i16| a.wrapping_sub(1);
141 let g1 = |a: i16| a.wrapping_mul(2);
142 let g2 = |a: i16| a / 2;
143
144 let left = VecF::invmap(x.clone(), |a| g1(f1(a)), |a| f2(g2(a)));
145 let right = VecF::invmap(VecF::invmap(x, f1, f2), g1, g2);
146 prop_assert_eq!(left, right);
147 }
148 }
149}