Skip to main content

karpal_core/
hkt.rs

1#[cfg(all(not(feature = "std"), feature = "alloc"))]
2use alloc::{boxed::Box, vec::Vec};
3use core::marker::PhantomData;
4
5/// Higher-Kinded Type encoding via GATs.
6///
7/// A type implementing `HKT` acts as a type-level function:
8/// given a type `T`, it produces `Self::Of<T>`.
9pub trait HKT {
10    type Of<T>;
11}
12
13/// Type constructor for `Option<T>`.
14pub struct OptionF;
15
16impl HKT for OptionF {
17    type Of<T> = Option<T>;
18}
19
20/// Type constructor for `Result<T, E>` (fixed error type `E`).
21pub struct ResultF<E> {
22    _marker: PhantomData<E>,
23}
24
25impl<E> HKT for ResultF<E> {
26    type Of<T> = Result<T, E>;
27}
28
29/// Type constructor for `Vec<T>`.
30#[cfg(any(feature = "std", feature = "alloc"))]
31pub struct VecF;
32
33#[cfg(any(feature = "std", feature = "alloc"))]
34impl HKT for VecF {
35    #[cfg(feature = "std")]
36    type Of<T> = Vec<T>;
37
38    #[cfg(all(not(feature = "std"), feature = "alloc"))]
39    type Of<T> = alloc::vec::Vec<T>;
40}
41
42/// Two-parameter type constructor (HKT for bifunctors / profunctors).
43pub trait HKT2 {
44    type P<A, B>;
45}
46
47/// Type constructor for `Result<A, B>` as a bifunctor (both parameters vary).
48pub struct ResultBF;
49
50impl HKT2 for ResultBF {
51    type P<A, B> = Result<B, A>;
52}
53
54/// Type constructor for `(A, B)` as a bifunctor.
55pub struct TupleF;
56
57impl HKT2 for TupleF {
58    type P<A, B> = (A, B);
59}
60
61/// Type constructor for the identity functor: `Of<T> = T`.
62pub struct IdentityF;
63
64impl HKT for IdentityF {
65    type Of<T> = T;
66}
67
68/// A non-empty vector: guaranteed to have at least one element.
69#[cfg(any(feature = "std", feature = "alloc"))]
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct NonEmptyVec<T> {
72    pub head: T,
73    pub tail: Vec<T>,
74}
75
76#[cfg(any(feature = "std", feature = "alloc"))]
77impl<T> NonEmptyVec<T> {
78    pub fn new(head: T, tail: Vec<T>) -> Self {
79        Self { head, tail }
80    }
81
82    pub fn singleton(value: T) -> Self {
83        Self {
84            head: value,
85            tail: Vec::new(),
86        }
87    }
88
89    pub fn len(&self) -> usize {
90        1 + self.tail.len()
91    }
92
93    pub fn is_empty(&self) -> bool {
94        false // always at least one element
95    }
96
97    pub fn iter(&self) -> impl Iterator<Item = &T> {
98        core::iter::once(&self.head).chain(self.tail.iter())
99    }
100
101    /// Collect all suffixes as a NonEmptyVec of NonEmptyVecs.
102    pub fn tails(&self) -> NonEmptyVec<NonEmptyVec<T>>
103    where
104        T: Clone,
105    {
106        let mut result_tail = Vec::new();
107        for i in 1..self.len() {
108            result_tail.push(NonEmptyVec::new(
109                self.tail[i - 1].clone(),
110                self.tail[i..].to_vec(),
111            ));
112        }
113        NonEmptyVec::new(self.clone(), result_tail)
114    }
115}
116
117/// Type constructor for `NonEmptyVec<T>`.
118#[cfg(any(feature = "std", feature = "alloc"))]
119pub struct NonEmptyVecF;
120
121#[cfg(any(feature = "std", feature = "alloc"))]
122impl HKT for NonEmptyVecF {
123    type Of<T> = NonEmptyVec<T>;
124}
125
126/// Type constructor for the Env comonad: `Of<T> = (E, T)`.
127pub struct EnvF<E>(PhantomData<E>);
128
129impl<E> HKT for EnvF<E> {
130    type Of<T> = (E, T);
131}
132
133/// Type constructor for the Store comonad: `Of<T> = (Box<dyn Fn(S) -> T>, S)`.
134#[cfg(any(feature = "std", feature = "alloc"))]
135pub struct StoreF<S>(PhantomData<S>);
136
137#[cfg(any(feature = "std", feature = "alloc"))]
138impl<S: 'static> HKT for StoreF<S> {
139    type Of<T> = (Box<dyn Fn(S) -> T>, S);
140}
141
142/// Type constructor for the Traced comonad: `Of<T> = Box<dyn Fn(M) -> T>`.
143#[cfg(any(feature = "std", feature = "alloc"))]
144pub struct TracedF<M>(PhantomData<M>);
145
146#[cfg(any(feature = "std", feature = "alloc"))]
147impl<M: 'static> HKT for TracedF<M> {
148    type Of<T> = Box<dyn Fn(M) -> T>;
149}
150
151/// Type constructor for the Reader functor: `Of<T> = Box<dyn Fn(E) -> T>`.
152///
153/// Isomorphic to `TracedF<E>` in representation, but serves a different
154/// semantic role: `ReaderF<E>` is the right adjoint of `EnvF<E>` in the
155/// product/exponential adjunction (`EnvF<E> ⊣ ReaderF<E>`).
156///
157/// `ReaderF<E>` cannot implement the generic `Functor` trait because
158/// `Box<dyn Fn>` requires `'static` bounds that the trait signature doesn't
159/// allow. Following the Lan pattern, inherent methods with `'static` bounds
160/// on the impl block provide the same functionality.
161#[cfg(any(feature = "std", feature = "alloc"))]
162pub struct ReaderF<E>(PhantomData<E>);
163
164#[cfg(any(feature = "std", feature = "alloc"))]
165impl<E: 'static> HKT for ReaderF<E> {
166    type Of<T> = Box<dyn Fn(E) -> T>;
167}
168
169#[cfg(any(feature = "std", feature = "alloc"))]
170impl<E: Clone + 'static> ReaderF<E> {
171    /// Functor `fmap` for Reader: post-compose a function.
172    ///
173    /// `fmap(f, reader) = |e| f(reader(e))`
174    pub fn fmap<A: 'static, B: 'static>(
175        fa: Box<dyn Fn(E) -> A>,
176        f: impl Fn(A) -> B + 'static,
177    ) -> Box<dyn Fn(E) -> B> {
178        Box::new(move |e| f(fa(e)))
179    }
180
181    /// Applicative `pure` for Reader: ignore the environment, return a constant.
182    ///
183    /// `pure(a) = |_e| a`
184    pub fn pure<A: Clone + 'static>(a: A) -> Box<dyn Fn(E) -> A> {
185        Box::new(move |_| a.clone())
186    }
187
188    /// Monadic `chain` (bind) for Reader: Kleisli composition.
189    ///
190    /// `chain(reader, f) = |e| f(reader(e))(e)`
191    pub fn chain<A: 'static, B: 'static>(
192        fa: Box<dyn Fn(E) -> A>,
193        f: impl Fn(A) -> Box<dyn Fn(E) -> B> + 'static,
194    ) -> Box<dyn Fn(E) -> B> {
195        Box::new(move |e: E| {
196            let a = fa(e.clone());
197            f(a)(e)
198        })
199    }
200
201    /// Reader-specific: access the environment directly.
202    ///
203    /// `ask() = |e| e`
204    pub fn ask() -> Box<dyn Fn(E) -> E> {
205        Box::new(|e| e)
206    }
207
208    /// Reader-specific: modify the environment before running a reader.
209    ///
210    /// `local(f, reader) = |e| reader(f(e))`
211    pub fn local<A: 'static>(
212        f: impl Fn(E) -> E + 'static,
213        reader: Box<dyn Fn(E) -> A>,
214    ) -> Box<dyn Fn(E) -> A> {
215        Box::new(move |e| reader(f(e)))
216    }
217}