Skip to main content

karpal_core/
coend.rs

1use crate::hkt::HKT2;
2
3/// A Coend for profunctor P: represents `∫^A P(A, A)`, i.e., `exists A. P(A, A)`.
4///
5/// In category theory, the coend of a profunctor `P: C^op × C -> Set` is the
6/// universal cowedge — a value `P(A, A)` for SOME (existentially hidden) type A.
7///
8/// Since Rust lacks first-class existential types, the type parameter A is
9/// exposed (following the same pragmatic approach as `Lan<G, H, A, B>` in
10/// karpal-free). Users should treat A as opaque when consuming coend values.
11///
12/// # Construction
13///
14/// ```rust
15/// use karpal_core::coend::Coend;
16/// use karpal_core::hkt::TupleF;
17///
18/// // A coend value: exists some A such that we have (A, A)
19/// let c: Coend<TupleF, i32> = Coend::new((42, 42));
20/// assert_eq!(c.value, (42, 42));
21/// ```
22pub struct Coend<P: HKT2, A> {
23    /// The diagonal value `P(A, A)` for the existentially quantified A.
24    pub value: P::P<A, A>,
25}
26
27impl<P: HKT2, A> Coend<P, A> {
28    /// Construct a coend value from a diagonal element.
29    pub fn new(value: P::P<A, A>) -> Self {
30        Coend { value }
31    }
32
33    /// Eliminate the coend by applying a function to the diagonal value.
34    ///
35    /// Given a function `P(A, A) -> R`, extract a result. This is the
36    /// concrete elimination form — in category theory, the eliminator
37    /// would be a dinatural transformation, but here the type parameter
38    /// A is exposed rather than existentially hidden.
39    pub fn elim<R>(self, f: impl FnOnce(P::P<A, A>) -> R) -> R {
40        f(self.value)
41    }
42}
43
44impl<P: HKT2, A: Clone> Clone for Coend<P, A>
45where
46    P::P<A, A>: Clone,
47{
48    fn clone(&self) -> Self {
49        Coend {
50            value: self.value.clone(),
51        }
52    }
53}
54
55impl<P: HKT2, A: PartialEq> PartialEq for Coend<P, A>
56where
57    P::P<A, A>: PartialEq,
58{
59    fn eq(&self, other: &Self) -> bool {
60        self.value == other.value
61    }
62}
63
64impl<P: HKT2, A: Eq> Eq for Coend<P, A> where P::P<A, A>: Eq {}
65
66impl<P: HKT2, A: core::fmt::Debug> core::fmt::Debug for Coend<P, A>
67where
68    P::P<A, A>: core::fmt::Debug,
69{
70    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71        f.debug_struct("Coend").field("value", &self.value).finish()
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::hkt::{ResultBF, TupleF};
79
80    #[test]
81    fn coend_tuple_new() {
82        let c: Coend<TupleF, i32> = Coend::new((1, 2));
83        assert_eq!(c.value, (1, 2));
84    }
85
86    #[test]
87    fn coend_tuple_elim() {
88        let c: Coend<TupleF, i32> = Coend::new((3, 4));
89        let sum = c.elim(|(a, b)| a + b);
90        assert_eq!(sum, 7);
91    }
92
93    #[test]
94    fn coend_result_new() {
95        let c: Coend<ResultBF, i32> = Coend::new(Ok(42));
96        assert_eq!(c.value, Ok(42));
97    }
98
99    #[test]
100    fn coend_clone() {
101        let c: Coend<TupleF, i32> = Coend::new((1, 2));
102        let c2 = c.clone();
103        assert_eq!(c, c2);
104    }
105
106    #[test]
107    fn coend_debug() {
108        let c: Coend<TupleF, i32> = Coend::new((1, 2));
109        let s = format!("{:?}", c);
110        assert!(s.contains("Coend"));
111        assert!(s.contains("(1, 2)"));
112    }
113}