Skip to main content

karpal_free/
ran.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use core::marker::PhantomData;
5
6use karpal_core::hkt::HKT;
7
8/// Right Kan Extension — `Ran G H A ≅ ∀R. (A → G R) → H R`.
9///
10/// Unlike most constructions in this crate, `Ran` is a **trait** rather than
11/// a concrete type. This is because the universal quantifier `∀R` requires a
12/// generic method, which cannot be made object-safe in Rust.
13///
14/// # Usage
15///
16/// Implement `Ran` for your own types to describe computations in CPS form:
17///
18/// ```rust,ignore
19/// struct MyRan(i32);
20///
21/// impl Ran<OptionF, OptionF> for MyRan {
22///     type Input = i32;
23///     fn run_ran<R>(&self, k: impl Fn(i32) -> Option<R>) -> Option<R> {
24///         k(self.0)
25///     }
26/// }
27/// ```
28///
29/// # Relationship to Codensity
30///
31/// `Ran<F, F, A>` specialised to `G = H = F` is exactly `Codensity<F, A>`.
32/// The `Codensity` type in this crate provides a concrete, ergonomic
33/// implementation of that specialisation.
34pub trait Ran<G: HKT, H: HKT> {
35    /// The input type (corresponds to `A` in `∀R. (A → G R) → H R`).
36    type Input;
37
38    /// Run the Ran: given a continuation `k: A → G R`, produce `H R`.
39    fn run_ran<R>(&self, k: impl Fn(Self::Input) -> G::Of<R>) -> H::Of<R>;
40}
41
42/// Map a function over a `Ran` implementation, producing a new `Ran`.
43///
44/// `ran_fmap(f: A → B)` transforms `Ran<G, H, Input=A>` into `Ran<G, H, Input=B>`:
45///
46/// ```text
47/// new.run_ran(k: B → G R) = old.run_ran(|a| k(f(a)))
48/// ```
49pub fn ran_fmap<G, H, A, B, T, F>(ran: T, f: F) -> RanMapped<G, H, A, B, T, F>
50where
51    G: HKT,
52    H: HKT,
53    T: Ran<G, H, Input = A>,
54    F: Fn(A) -> B,
55{
56    RanMapped {
57        ran,
58        f,
59        _marker: PhantomData,
60    }
61}
62
63/// A `Ran` with a mapped input, produced by [`ran_fmap`].
64pub struct RanMapped<G: HKT, H: HKT, A, B, T, F> {
65    ran: T,
66    f: F,
67    _marker: PhantomData<(G, H, A, B)>,
68}
69
70impl<G, H, A, B, T, F> Ran<G, H> for RanMapped<G, H, A, B, T, F>
71where
72    G: HKT,
73    H: HKT,
74    T: Ran<G, H, Input = A>,
75    F: Fn(A) -> B,
76{
77    type Input = B;
78
79    fn run_ran<R>(&self, k: impl Fn(B) -> G::Of<R>) -> H::Of<R> {
80        let f_ref = &self.f;
81        self.ran.run_ran(move |a| k(f_ref(a)))
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use karpal_core::hkt::OptionF;
89
90    struct SimpleRan(i32);
91
92    impl Ran<OptionF, OptionF> for SimpleRan {
93        type Input = i32;
94        fn run_ran<R>(&self, k: impl Fn(i32) -> Option<R>) -> Option<R> {
95            k(self.0)
96        }
97    }
98
99    #[test]
100    fn basic_ran() {
101        let ran = SimpleRan(42);
102        let result = ran.run_ran(|x| Some(x * 2));
103        assert_eq!(result, Some(84));
104    }
105
106    #[test]
107    fn ran_with_none() {
108        let ran = SimpleRan(0);
109        let result: Option<i32> = ran.run_ran(|_| None);
110        assert_eq!(result, None);
111    }
112
113    #[test]
114    fn ran_fmap_basic() {
115        let ran = SimpleRan(10);
116        let mapped = ran_fmap(ran, |x| x + 5);
117        let result = mapped.run_ran(|x| Some(x * 2));
118        // mapped.run_ran(k) = ran.run_ran(|a| k(f(a)))
119        // = ran.run_ran(|a| k(a + 5))
120        // = k(10 + 5) = k(15) = Some(15 * 2) = Some(30)
121        assert_eq!(result, Some(30));
122    }
123
124    #[test]
125    fn ran_fmap_identity() {
126        let ran = SimpleRan(7);
127        let mapped = ran_fmap(ran, |x: i32| x);
128        let result = mapped.run_ran(Some);
129        assert_eq!(result, Some(7));
130    }
131
132    #[test]
133    fn ran_fmap_composition() {
134        let f = |x: i32| x + 1;
135        let g = |x: i32| x * 2;
136
137        let left = ran_fmap(SimpleRan(5), move |a| g(f(a)));
138        let right = ran_fmap(ran_fmap(SimpleRan(5), f), g);
139
140        let left_result = left.run_ran(Some);
141        let right_result = right.run_ran(Some);
142        assert_eq!(left_result, right_result);
143    }
144
145    // Demonstrate relationship to Codensity: Ran<F, F, A> ≅ Codensity<F, A>
146    #[test]
147    fn ran_as_codensity() {
148        // Ran<OptionF, OptionF> with Input = i32
149        // is equivalent to Codensity<OptionF, i32>:
150        //   ∀R. (i32 → Option R) → Option R
151        let ran = SimpleRan(42);
152        // "lower" via pure: k = Some
153        let result = ran.run_ran(Some);
154        assert_eq!(result, Some(42));
155    }
156
157    // Test with a different G and H
158    struct VecToOption(Vec<i32>);
159
160    impl Ran<OptionF, OptionF> for VecToOption {
161        type Input = i32;
162        fn run_ran<R>(&self, k: impl Fn(i32) -> Option<R>) -> Option<R> {
163            // Apply k to the first element
164            self.0.first().and_then(|&x| k(x))
165        }
166    }
167
168    #[test]
169    fn ran_different_impl() {
170        let ran = VecToOption(vec![10, 20, 30]);
171        let result = ran.run_ran(|x| Some(x.to_string()));
172        assert_eq!(result, Some("10".to_string()));
173    }
174
175    #[test]
176    fn ran_different_impl_empty() {
177        let ran = VecToOption(vec![]);
178        let result: Option<String> = ran.run_ran(|x| Some(x.to_string()));
179        assert_eq!(result, None);
180    }
181}
182
183#[cfg(test)]
184mod law_tests {
185    use super::*;
186    use karpal_core::hkt::OptionF;
187    use proptest::prelude::*;
188
189    struct PureRan(i32);
190
191    impl Ran<OptionF, OptionF> for PureRan {
192        type Input = i32;
193        fn run_ran<R>(&self, k: impl Fn(i32) -> Option<R>) -> Option<R> {
194            k(self.0)
195        }
196    }
197
198    proptest! {
199        // Functor identity: ran_fmap(id, r).run(k) == r.run(k)
200        #[test]
201        fn functor_identity(x in any::<i32>()) {
202            let original = PureRan(x).run_ran(Some);
203            let mapped = ran_fmap(PureRan(x), |a: i32| a).run_ran(Some);
204            prop_assert_eq!(original, mapped);
205        }
206
207        // Functor composition: ran_fmap(g . f) == ran_fmap(g) . ran_fmap(f)
208        #[test]
209        fn functor_composition(x in any::<i32>()) {
210            let f = |a: i32| a.wrapping_add(1);
211            let g = |a: i32| a.wrapping_mul(2);
212
213            let left = ran_fmap(PureRan(x), move |a| g(f(a))).run_ran(Some);
214            let right = ran_fmap(ran_fmap(PureRan(x), f), g).run_ran(Some);
215            prop_assert_eq!(left, right);
216        }
217    }
218}