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