1use crate::fold::Fold;
5use crate::getter::Getter;
6use crate::optic::Optic;
7use crate::review::Review;
8use karpal_profunctor::Profunctor;
9
10pub struct Iso<S, T, A, B> {
16 forward: fn(&S) -> A,
17 backward: fn(B) -> T,
18}
19
20pub type SimpleIso<S, A> = Iso<S, S, A, A>;
22
23impl<S, T, A, B> Optic for Iso<S, T, A, B> {}
24
25impl<S, T, A, B> Iso<S, T, A, B> {
26 pub fn new(forward: fn(&S) -> A, backward: fn(B) -> T) -> Self {
27 Self { forward, backward }
28 }
29
30 pub fn get(&self, s: &S) -> A {
32 (self.forward)(s)
33 }
34
35 pub fn review(&self, b: B) -> T {
37 (self.backward)(b)
38 }
39
40 pub fn set(&self, _s: S, b: B) -> T {
42 (self.backward)(b)
43 }
44
45 pub fn transform<P: Profunctor>(&self, pab: P::P<A, B>) -> P::P<S, T>
49 where
50 S: 'static,
51 T: 'static,
52 A: 'static,
53 B: 'static,
54 {
55 let fwd = self.forward;
56 let bwd = self.backward;
57 P::dimap(move |s: S| fwd(&s), bwd, pab)
58 }
59
60 pub fn to_getter(&self) -> Getter<S, A> {
62 Getter::new(self.forward)
63 }
64
65 pub fn to_review(&self) -> Review<T, B> {
67 Review::new(self.backward)
68 }
69
70 pub fn to_fold(&self) -> Fold<S, A>
72 where
73 S: 'static,
74 A: 'static,
75 {
76 let fwd = self.forward;
77 Fold::new(move |s| vec![fwd(s)])
78 }
79}
80
81impl<S: Clone, T, A, B> Iso<S, T, A, B> {
82 pub fn over(&self, s: S, f: impl FnOnce(A) -> B) -> T {
84 (self.backward)(f((self.forward)(&s)))
85 }
86
87 pub fn to_lens(&self) -> crate::lens::ComposedLens<S, T, A, B>
90 where
91 S: 'static,
92 T: 'static,
93 A: 'static,
94 B: 'static,
95 {
96 let fwd = self.forward;
97 let bwd = self.backward;
98 crate::lens::ComposedLens::from_fns(Box::new(fwd), Box::new(move |_s, b| bwd(b)))
99 }
100
101 pub fn to_setter(&self) -> crate::setter::Setter<S, T, A, B>
103 where
104 S: 'static,
105 T: 'static,
106 A: 'static,
107 B: 'static,
108 {
109 let fwd = self.forward;
110 let bwd = self.backward;
111 crate::setter::Setter::new(move |s: S, f: &dyn Fn(A) -> B| bwd(f(fwd(&s))))
112 }
113
114 pub fn to_traversal(&self) -> crate::traversal::Traversal<S, T, A, B>
116 where
117 S: 'static,
118 T: 'static,
119 A: 'static,
120 B: 'static,
121 {
122 let fwd = self.forward;
123 let bwd = self.backward;
124 crate::traversal::Traversal::new(move |s| vec![fwd(s)], move |s, f| bwd(f(fwd(&s))))
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use karpal_profunctor::FnP;
132 use proptest::prelude::*;
133
134 fn celsius_fahrenheit_iso() -> SimpleIso<f64, f64> {
135 Iso::new(
136 |c: &f64| c * 9.0 / 5.0 + 32.0,
137 |f: f64| (f - 32.0) * 5.0 / 9.0,
138 )
139 }
140
141 fn string_bytes_iso() -> SimpleIso<String, Vec<u8>> {
142 Iso::new(
143 |s: &String| s.clone().into_bytes(),
144 |b: Vec<u8>| String::from_utf8(b).unwrap(),
145 )
146 }
147
148 #[test]
149 fn iso_get() {
150 let iso = celsius_fahrenheit_iso();
151 let result = iso.get(&100.0);
152 assert!((result - 212.0).abs() < 1e-10);
153 }
154
155 #[test]
156 fn iso_review() {
157 let iso = celsius_fahrenheit_iso();
158 let result = iso.review(212.0);
159 assert!((result - 100.0).abs() < 1e-10);
160 }
161
162 #[test]
163 fn iso_over() {
164 let iso = celsius_fahrenheit_iso();
165 let result = iso.over(0.0, |f| f + 18.0);
167 assert!((result - 10.0).abs() < 1e-10);
168 }
169
170 #[test]
171 fn iso_set() {
172 let iso = celsius_fahrenheit_iso();
173 let result = iso.set(999.0, 32.0); assert!((result - 0.0).abs() < 1e-10);
175 }
176
177 #[test]
178 fn iso_transform_fnp() {
179 let iso = string_bytes_iso();
180 let upper: Box<dyn Fn(Vec<u8>) -> Vec<u8>> =
181 Box::new(|bytes| bytes.into_iter().map(|b| b.to_ascii_uppercase()).collect());
182 let f = iso.transform::<FnP>(upper);
183 assert_eq!(f("hello".to_string()), "HELLO");
184 }
185
186 #[test]
187 fn iso_to_lens() {
188 let iso = string_bytes_iso();
189 let lens = iso.to_lens();
190 assert_eq!(lens.get(&"hi".to_string()), vec![b'h', b'i']);
191 assert_eq!(lens.set("x".to_string(), vec![b'a', b'b']), "ab");
192 }
193
194 #[test]
195 fn iso_to_getter() {
196 let iso = string_bytes_iso();
197 let getter = iso.to_getter();
198 assert_eq!(getter.get(&"hi".to_string()), vec![b'h', b'i']);
199 }
200
201 #[test]
202 fn iso_to_review() {
203 let iso = string_bytes_iso();
204 let review = iso.to_review();
205 assert_eq!(review.review(vec![b'a', b'b']), "ab");
206 }
207
208 proptest! {
210 #[test]
211 fn law_roundtrip_forward_backward(bytes in prop::collection::vec(any::<u8>(), 0..20)) {
212 let iso = string_bytes_iso();
213 if let Ok(s) = String::from_utf8(bytes) {
215 let result = iso.review(iso.get(&s));
216 prop_assert_eq!(result, s);
217 }
218 }
219 }
220
221 proptest! {
223 #[test]
224 fn law_roundtrip_backward_forward(s in "[a-z]{0,20}") {
225 let iso = string_bytes_iso();
226 let result = iso.get(&iso.review(iso.get(&s)));
227 prop_assert_eq!(result, iso.get(&s));
228 }
229 }
230}