Skip to main content

karpal_profunctor/
fn_profunctor.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3
4use crate::choice::Choice;
5use crate::profunctor::{HKT2, Profunctor};
6use crate::strong::Strong;
7use crate::traversing::Traversing;
8
9/// Marker type whose `P<A, B>` is `Box<dyn Fn(A) -> B>`.
10///
11/// This is the canonical `Profunctor` instance: the function arrow.
12pub struct FnP;
13
14impl HKT2 for FnP {
15    type P<A, B> = Box<dyn Fn(A) -> B>;
16}
17
18impl Profunctor for FnP {
19    fn dimap<A: 'static, B: 'static, C, D>(
20        f: impl Fn(C) -> A + 'static,
21        g: impl Fn(B) -> D + 'static,
22        pab: Box<dyn Fn(A) -> B>,
23    ) -> Box<dyn Fn(C) -> D> {
24        Box::new(move |c| g(pab(f(c))))
25    }
26}
27
28impl Strong for FnP {
29    fn first<A, B, C>(pab: Box<dyn Fn(A) -> B>) -> Box<dyn Fn((A, C)) -> (B, C)>
30    where
31        A: 'static,
32        B: 'static,
33        C: 'static,
34    {
35        Box::new(move |(a, c)| (pab(a), c))
36    }
37
38    fn second<A, B, C>(pab: Box<dyn Fn(A) -> B>) -> Box<dyn Fn((C, A)) -> (C, B)>
39    where
40        A: 'static,
41        B: 'static,
42        C: 'static,
43    {
44        Box::new(move |(c, a)| (c, pab(a)))
45    }
46}
47
48impl Traversing for FnP {
49    fn wander<S, T, A, B>(
50        _get_all: impl Fn(&S) -> Vec<A> + 'static,
51        modify_all: impl Fn(S, &dyn Fn(A) -> B) -> T + 'static,
52        pab: Box<dyn Fn(A) -> B>,
53    ) -> Box<dyn Fn(S) -> T>
54    where
55        S: 'static,
56        T: 'static,
57        A: 'static,
58        B: 'static,
59    {
60        Box::new(move |s| modify_all(s, &*pab))
61    }
62}
63
64impl Choice for FnP {
65    fn left<A, B, C>(pab: Box<dyn Fn(A) -> B>) -> Box<dyn Fn(Result<A, C>) -> Result<B, C>>
66    where
67        A: 'static,
68        B: 'static,
69        C: 'static,
70    {
71        Box::new(move |r| match r {
72            Ok(a) => Ok(pab(a)),
73            Err(c) => Err(c),
74        })
75    }
76
77    fn right<A, B, C>(pab: Box<dyn Fn(A) -> B>) -> Box<dyn Fn(Result<C, A>) -> Result<C, B>>
78    where
79        A: 'static,
80        B: 'static,
81        C: 'static,
82    {
83        Box::new(move |r| match r {
84            Ok(c) => Ok(c),
85            Err(a) => Err(pab(a)),
86        })
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn fnp_dimap() {
96        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
97        let f = FnP::dimap(|s: &str| s.len() as i32, |n: i32| n.to_string(), double);
98        assert_eq!(f("hello"), "10");
99    }
100
101    #[test]
102    fn fnp_first() {
103        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
104        let f = FnP::first::<i32, i32, &str>(double);
105        assert_eq!(f((5, "hi")), (10, "hi"));
106    }
107
108    #[test]
109    fn fnp_second() {
110        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
111        let f = FnP::second::<i32, i32, &str>(double);
112        assert_eq!(f(("hi", 5)), ("hi", 10));
113    }
114
115    #[test]
116    fn fnp_left() {
117        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
118        let f = FnP::left::<i32, i32, &str>(double);
119        assert_eq!(f(Ok(5)), Ok(10));
120        assert_eq!(f(Err("nope")), Err("nope"));
121    }
122
123    #[test]
124    fn fnp_right() {
125        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
126        let f = FnP::right::<i32, i32, &str>(double);
127        assert_eq!(f(Err(5)), Err(10));
128        assert_eq!(f(Ok("yep")), Ok("yep"));
129    }
130}
131
132#[cfg(test)]
133mod law_tests {
134    use super::*;
135    use proptest::prelude::*;
136
137    proptest! {
138        #[test]
139        fn profunctor_identity(x in any::<i32>()) {
140            let id_fn: Box<dyn Fn(i32) -> i32> = Box::new(|a| a);
141            let dimapped = FnP::dimap(|a: i32| a, |b: i32| b, id_fn);
142            prop_assert_eq!(dimapped(x), x);
143        }
144
145        #[test]
146        fn profunctor_composition(x in any::<i32>()) {
147            let base: Box<dyn Fn(i32) -> i32> = Box::new(|a| a);
148
149            let f = |a: i32| a.wrapping_add(1);
150            let g = |a: i32| a.wrapping_mul(2);
151            let h = |a: i32| a.wrapping_add(3);
152            let i_fn = |a: i32| a.wrapping_mul(4);
153
154            // dimap(f . g, h . i, p)
155            let left = FnP::dimap(
156                move |a: i32| f(g(a)),
157                move |b: i32| h(i_fn(b)),
158                base,
159            );
160
161            let base2: Box<dyn Fn(i32) -> i32> = Box::new(|a| a);
162            // dimap(g, h, dimap(f, i, p))
163            let inner = FnP::dimap(f, i_fn, base2);
164            let right = FnP::dimap(g, h, inner);
165
166            prop_assert_eq!(left(x), right(x));
167        }
168    }
169}