Skip to main content

karpal_profunctor/
forget.rs

1use 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
12/// Marker type whose `P<A, B> = Box<dyn Fn(A) -> R>`.
13///
14/// `B` is phantom — the second argument to `dimap` is ignored.
15/// This profunctor "forgets" the output and extracts a summary value.
16pub 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        // g should be completely ignored
108        let result = ForgetF::dimap(|x: i32| x + 1, |_: String| 999i32, extract);
109        assert_eq!(result(4), "5"); // f(4) = 5, then extract(5) = "5"
110    }
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); // Monoid::empty for i32
138    }
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        // ForgetF<R>::P<A, B> = Box<dyn Fn(A) -> R>, B is completely phantom
150        let extract: Box<dyn Fn(i32) -> String> = Box::new(|x| format!("got {x}"));
151        // B can be anything — it's never used
152        let result = <ForgetF<String> as Profunctor>::dimap(
153            |x: i32| x,
154            |_: String| vec![1, 2, 3], // g produces Vec<i32>, totally ignored
155            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}