Skip to main content

karpal_profunctor/
fn_profunctor.rs

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