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