Skip to main content

karpal_core/
hkt.rs

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