1use crate::fold::Fold;
5use crate::optic::Optic;
6use crate::review::Review;
7use crate::setter::Setter;
8use crate::traversal::Traversal;
9use karpal_profunctor::choice::Choice;
10
11pub struct Prism<S, T, A, B> {
21 match_: fn(S) -> Result<A, T>,
23 build: fn(B) -> T,
25}
26
27pub type SimplePrism<S, A> = Prism<S, S, A, A>;
29
30impl<S, T, A, B> Optic for Prism<S, T, A, B> {}
31
32impl<S, T, A, B> Prism<S, T, A, B> {
33 pub fn new(match_: fn(S) -> Result<A, T>, build: fn(B) -> T) -> Self {
34 Self { match_, build }
35 }
36
37 pub fn preview(&self, s: &S) -> Option<A>
39 where
40 S: Clone,
41 {
42 (self.match_)(s.clone()).ok()
43 }
44
45 pub fn review(&self, b: B) -> T {
47 (self.build)(b)
48 }
49
50 pub fn set(&self, s: S, b: B) -> T {
52 match (self.match_)(s) {
53 Ok(_) => (self.build)(b),
54 Err(t) => t,
55 }
56 }
57
58 pub fn over(&self, s: S, f: impl FnOnce(A) -> B) -> T {
60 match (self.match_)(s) {
61 Ok(a) => (self.build)(f(a)),
62 Err(t) => t,
63 }
64 }
65
66 pub fn to_review(&self) -> Review<T, B> {
68 Review::new(self.build)
69 }
70
71 pub fn to_setter(&self) -> Setter<S, T, A, B>
73 where
74 S: 'static,
75 T: 'static,
76 A: 'static,
77 B: 'static,
78 {
79 let match_ = self.match_;
80 let build = self.build;
81 Setter::new(move |s: S, f: &dyn Fn(A) -> B| match match_(s) {
82 Ok(a) => build(f(a)),
83 Err(t) => t,
84 })
85 }
86
87 pub fn to_traversal(&self) -> Traversal<S, T, A, B>
89 where
90 S: Clone + 'static,
91 T: 'static,
92 A: 'static,
93 B: 'static,
94 {
95 let match_ = self.match_;
96 let build = self.build;
97 Traversal::new(
98 move |s: &S| match match_(s.clone()) {
99 Ok(a) => vec![a],
100 Err(_) => vec![],
101 },
102 move |s: S, f: &dyn Fn(A) -> B| match match_(s) {
103 Ok(a) => build(f(a)),
104 Err(t) => t,
105 },
106 )
107 }
108
109 pub fn to_fold(&self) -> Fold<S, A>
111 where
112 S: Clone + 'static,
113 T: 'static,
114 A: 'static,
115 {
116 let match_ = self.match_;
117 Fold::new(move |s: &S| match match_(s.clone()) {
118 Ok(a) => vec![a],
119 Err(_) => vec![],
120 })
121 }
122
123 pub fn transform<P: Choice>(&self, pab: P::P<A, B>) -> P::P<S, T>
136 where
137 S: 'static,
138 T: 'static,
139 A: 'static,
140 B: 'static,
141 {
142 let match_ = self.match_;
143 let build = self.build;
144 let right_pab = P::right::<A, B, T>(pab);
145 P::dimap(
146 move |s: S| match match_(s) {
147 Ok(a) => Err(a), Err(t) => Ok(t), },
150 move |result: Result<T, B>| match result {
151 Ok(t) => t, Err(b) => build(b), },
154 right_pab,
155 )
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use karpal_profunctor::FnP;
163 use proptest::prelude::*;
164
165 #[derive(Debug, Clone, PartialEq)]
166 enum Shape {
167 Circle(f64),
168 Rectangle(f64, f64),
169 }
170
171 fn circle_prism() -> SimplePrism<Shape, f64> {
172 Prism::new(
173 |s| match s {
174 Shape::Circle(r) => Ok(r),
175 Shape::Rectangle(w, h) => Err(Shape::Rectangle(w, h)),
176 },
177 Shape::Circle,
178 )
179 }
180
181 fn sample_circle() -> Shape {
182 Shape::Circle(5.0)
183 }
184
185 fn sample_rect() -> Shape {
186 Shape::Rectangle(3.0, 4.0)
187 }
188
189 #[test]
192 fn preview_match() {
193 let prism = circle_prism();
194 assert_eq!(prism.preview(&sample_circle()), Some(5.0));
195 }
196
197 #[test]
198 fn preview_no_match() {
199 let prism = circle_prism();
200 assert_eq!(prism.preview(&sample_rect()), None);
201 }
202
203 #[test]
204 fn review() {
205 let prism = circle_prism();
206 assert_eq!(prism.review(10.0), Shape::Circle(10.0));
207 }
208
209 #[test]
210 fn set_match() {
211 let prism = circle_prism();
212 assert_eq!(prism.set(sample_circle(), 10.0), Shape::Circle(10.0));
213 }
214
215 #[test]
216 fn set_no_match() {
217 let prism = circle_prism();
218 assert_eq!(prism.set(sample_rect(), 10.0), sample_rect());
219 }
220
221 #[test]
222 fn over_match() {
223 let prism = circle_prism();
224 assert_eq!(
225 prism.over(sample_circle(), |r| r * 2.0),
226 Shape::Circle(10.0)
227 );
228 }
229
230 #[test]
231 fn over_no_match() {
232 let prism = circle_prism();
233 assert_eq!(prism.over(sample_rect(), |r| r * 2.0), sample_rect());
234 }
235
236 fn finite_f64() -> impl Strategy<Value = f64> {
240 (-1e6f64..1e6f64).prop_filter("finite", |v| v.is_finite())
241 }
242
243 proptest! {
245 #[test]
246 fn law_review_preview(b in finite_f64()) {
247 let prism = circle_prism();
248 let s = prism.review(b);
249 prop_assert_eq!(prism.preview(&s), Some(b));
250 }
251 }
252
253 proptest! {
255 #[test]
256 fn law_preview_review(r in finite_f64()) {
257 let prism = circle_prism();
258 let s = Shape::Circle(r);
259 if let Some(a) = prism.preview(&s) {
260 prop_assert_eq!(prism.review(a), s);
261 }
262 }
263 }
264
265 proptest! {
267 #[test]
268 fn law_over_identity_circle(r in finite_f64()) {
269 let prism = circle_prism();
270 let s = Shape::Circle(r);
271 prop_assert_eq!(prism.over(s.clone(), |x| x), s);
272 }
273
274 #[test]
275 fn law_over_identity_rect(w in finite_f64(), h in finite_f64()) {
276 let prism = circle_prism();
277 let s = Shape::Rectangle(w, h);
278 prop_assert_eq!(prism.over(s.clone(), |x| x), s);
279 }
280 }
281
282 #[test]
285 fn transform_fnp_match() {
286 let prism = circle_prism();
287 let double: Box<dyn Fn(f64) -> f64> = Box::new(|r| r * 2.0);
288 let transform_fn = prism.transform::<FnP>(double);
289 assert_eq!(transform_fn(sample_circle()), Shape::Circle(10.0));
290 }
291
292 #[test]
293 fn transform_fnp_no_match() {
294 let prism = circle_prism();
295 let double: Box<dyn Fn(f64) -> f64> = Box::new(|r| r * 2.0);
296 let transform_fn = prism.transform::<FnP>(double);
297 assert_eq!(transform_fn(sample_rect()), sample_rect());
298 }
299}