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