karpal_profunctor/
forget.rs1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::marker::PhantomData;
4
5use karpal_core::Monoid;
6
7use crate::choice::Choice;
8use crate::profunctor::{HKT2, Profunctor};
9use crate::strong::Strong;
10use crate::traversing::Traversing;
11
12pub struct ForgetF<R>(PhantomData<R>);
17
18impl<R: 'static> HKT2 for ForgetF<R> {
19 type P<A, B> = Box<dyn Fn(A) -> R>;
20}
21
22impl<R: 'static> Profunctor for ForgetF<R> {
23 fn dimap<A: 'static, B: 'static, C, D>(
24 f: impl Fn(C) -> A + 'static,
25 _g: impl Fn(B) -> D + 'static,
26 pab: Box<dyn Fn(A) -> R>,
27 ) -> Box<dyn Fn(C) -> R> {
28 Box::new(move |c| pab(f(c)))
29 }
30}
31
32impl<R: 'static> Strong for ForgetF<R> {
33 fn first<A, B, C>(pab: Box<dyn Fn(A) -> R>) -> Box<dyn Fn((A, C)) -> R>
34 where
35 A: 'static,
36 B: 'static,
37 C: 'static,
38 {
39 Box::new(move |(a, _)| pab(a))
40 }
41
42 fn second<A, B, C>(pab: Box<dyn Fn(A) -> R>) -> Box<dyn Fn((C, A)) -> R>
43 where
44 A: 'static,
45 B: 'static,
46 C: 'static,
47 {
48 Box::new(move |(_, a)| pab(a))
49 }
50}
51
52impl<R: Monoid + 'static> Choice for ForgetF<R> {
53 fn left<A, B, C>(pab: Box<dyn Fn(A) -> R>) -> Box<dyn Fn(Result<A, C>) -> R>
54 where
55 A: 'static,
56 B: 'static,
57 C: 'static,
58 {
59 Box::new(move |r| match r {
60 Ok(a) => pab(a),
61 Err(_) => R::empty(),
62 })
63 }
64
65 fn right<A, B, C>(pab: Box<dyn Fn(A) -> R>) -> Box<dyn Fn(Result<C, A>) -> R>
66 where
67 A: 'static,
68 B: 'static,
69 C: 'static,
70 {
71 Box::new(move |r| match r {
72 Ok(_) => R::empty(),
73 Err(a) => pab(a),
74 })
75 }
76}
77
78impl<R: Monoid + 'static> Traversing for ForgetF<R> {
79 fn wander<S, T, A, B>(
80 get_all: impl Fn(&S) -> Vec<A> + 'static,
81 _modify_all: impl Fn(S, &dyn Fn(A) -> B) -> T + 'static,
82 pab: Self::P<A, B>,
83 ) -> Self::P<S, T>
84 where
85 S: 'static,
86 T: 'static,
87 A: 'static,
88 B: 'static,
89 {
90 Box::new(move |s: S| {
91 get_all(&s)
92 .into_iter()
93 .map(&*pab)
94 .fold(R::empty(), |acc, r| acc.combine(r))
95 })
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use proptest::prelude::*;
103
104 #[test]
105 fn forget_dimap_ignores_g() {
106 let extract: Box<dyn Fn(i32) -> String> = Box::new(|x| x.to_string());
107 let result = ForgetF::dimap(|x: i32| x + 1, |_: String| 999i32, extract);
109 assert_eq!(result(4), "5"); }
111
112 #[test]
113 fn forget_strong_first() {
114 let extract: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 10);
115 let f = <ForgetF<i32> as Strong>::first::<i32, i32, &str>(extract);
116 assert_eq!(f((3, "hi")), 30);
117 }
118
119 #[test]
120 fn forget_strong_second() {
121 let extract: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 10);
122 let f = <ForgetF<i32> as Strong>::second::<i32, i32, &str>(extract);
123 assert_eq!(f(("hi", 3)), 30);
124 }
125
126 #[test]
127 fn forget_choice_left_match() {
128 let extract: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 10);
129 let f = <ForgetF<i32> as Choice>::left::<i32, i32, &str>(extract);
130 assert_eq!(f(Ok(3)), 30);
131 }
132
133 #[test]
134 fn forget_choice_left_miss() {
135 let extract: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 10);
136 let f = <ForgetF<i32> as Choice>::left::<i32, i32, &str>(extract);
137 assert_eq!(f(Err("nope")), 0); }
139
140 #[test]
141 fn forget_choice_right_match() {
142 let extract: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 10);
143 let f = <ForgetF<i32> as Choice>::right::<i32, i32, &str>(extract);
144 assert_eq!(f(Err(3)), 30);
145 }
146
147 #[test]
148 fn forget_phantom_b_verification() {
149 let extract: Box<dyn Fn(i32) -> String> = Box::new(|x| format!("got {x}"));
151 let result = <ForgetF<String> as Profunctor>::dimap(
153 |x: i32| x,
154 |_: String| vec![1, 2, 3], extract,
156 );
157 assert_eq!(result(42), "got 42");
158 }
159
160 proptest! {
161 #[test]
162 fn forget_profunctor_identity(x in any::<i32>()) {
163 let id_fn: Box<dyn Fn(i32) -> i32> = Box::new(|a| a);
164 let dimapped = <ForgetF<i32> as Profunctor>::dimap(|a: i32| a, |b: i32| b, id_fn);
165 prop_assert_eq!(dimapped(x), x);
166 }
167 }
168}