Skip to main content

karpal_core/
hkt.rs

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