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