Skip to main content

karpal_core/
coend.rs

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