Skip to main content

fp_library/
lib.rs

1#![warn(missing_docs)]
2#![allow(clippy::tabs_in_doc_comments)]
3
4//! A functional programming library for Rust featuring your favourite higher-kinded types and type classes.
5//!
6//! ## Motivation
7//!
8//! Rust is a multi-paradigm language with strong functional programming features like iterators, closures, and algebraic data types. However, it lacks native support for **Higher-Kinded Types (HKT)**, which limits the ability to write generic code that abstracts over type constructors (e.g., writing a function that works for any `Monad`, whether it's `Option`, `Result`, or `Vec`).
9//!
10//! `fp-library` aims to bridge this gap by providing:
11//!
12//! 1.  A robust encoding of HKTs in stable Rust.
13//! 2.  A comprehensive set of standard type classes (`Functor`, `Monad`, `Traversable`, etc.).
14//! 3.  Zero-cost abstractions that respect Rust's performance characteristics.
15//!
16//! ## Features
17//!
18//! - **Higher-Kinded Types (HKT):** Implemented using lightweight higher-kinded polymorphism (type-level defunctionalization/brands).
19//! - **Macros:** Procedural macros for working with HKTs and monadic code:
20//!   - **HKT:** `trait_kind!`, `impl_kind!`, `Apply!`, `#[kind]` for defining and applying higher-kinded type encodings
21//!   - **Do-Notation:** `m_do!` for monadic do-notation, `a_do!` for applicative do-notation
22//! - **Type Classes:** A comprehensive collection of standard type classes including:
23//!   - **Core:** `Functor`, `Contravariant`, `Pointed`, `Applicative`, `Semiapplicative`, `Monad`, `Semimonad`, `Semigroup`, `Monoid`, `Foldable`, `Traversable`, `Alt`, `Plus`, `Alternative`
24//!   - **Applicative Utilities:** `Lift`, `ApplyFirst`, `ApplySecond`
25//!   - **Monad Utilities:** `MonadPlus`, `MonadRec`, `Extract`
26//!   - **Comonads:** `Extend`, `Comonad`
27//!   - **Bifunctors:** `Bifunctor`, `Bifoldable`, `Bitraversable`
28//!   - **Collections:** `Compactable`, `Filterable`, `Witherable`
29//!   - **Indexed:** `WithIndex`, `FunctorWithIndex`, `FoldableWithIndex`, `TraversableWithIndex`
30//!   - **Category Theory:** `Category`, `Semigroupoid`, `Profunctor`, `Strong`, `Choice`, `Closed`, `Cochoice`, `Costrong`, `Wander`
31//!   - **Laziness & Effects:** `RefFunctor`, `SendRefFunctor`, `Deferrable`, `SendDeferrable`, `LazyConfig`, `TryLazyConfig`
32//!   - **Parallel:** `ParFunctor`, `ParCompactable`, `ParFilterable`, `ParFoldable`, `ParFunctorWithIndex`, `ParFoldableWithIndex`
33//! - **Function & Pointer Abstractions:** Traits for abstracting over function wrappers and reference counting:
34//!   - **Functions:** `Function`, `CloneableFn`, `SendCloneableFn`, `UnsizedCoercible`, `SendUnsizedCoercible`
35//!   - **Pointers:** `Pointer`, `RefCountedPointer`, `SendRefCountedPointer`
36//! - **Optics:** Composable data accessors using profunctor encoding (port of PureScript's `purescript-profunctor-lenses`):
37//!   - **Iso / IsoPrime:** Isomorphism between two types
38//!   - **Lens / LensPrime:** Focus on a field within a product type
39//!   - **Prism / PrismPrime:** Focus on a variant within a sum type
40//!   - **AffineTraversal / AffineTraversalPrime:** Optional focusing (combines Lens + Prism)
41//!   - **Traversal / TraversalPrime:** Focus on multiple values
42//!   - **Getter / GetterPrime:** Read-only access
43//!   - **Setter / SetterPrime:** Write-only modification
44//!   - **Fold / FoldPrime:** Collecting multiple values (read-only)
45//!   - **Review / ReviewPrime:** Constructing values
46//!   - **Grate / GratePrime:** Closed/zipping optics
47//!   - **Indexed variants:** `IndexedLens`, `IndexedTraversal`, `IndexedGetter`, `IndexedFold`, `IndexedSetter`
48//!   - **Composition:** `Composed` struct and `optics_compose` for zero-cost optic composition
49//! - **Numeric Algebra:** `Semiring`, `Ring`, `CommutativeRing`, `EuclideanRing`, `DivisionRing`, `Field`, `HeytingAlgebra`
50//! - **Newtype Wrappers:** `Additive`, `Multiplicative`, `Conjunctive`, `Disjunctive`, `First`, `Last`, `Dual`
51//! - **Helper Functions:** Standard FP utilities:
52//!   - `compose`, `constant`, `flip`, `identity`, `on`, `pipe`
53//! - **Data Types:** Implementations for standard and custom types:
54//!   - **Standard Library:** `Option`, `Result`, `Vec`, `String`
55//!   - **Laziness, Memoization & Stack Safety:** `Lazy` (`RcLazy`, `ArcLazy`), `Thunk`, `SendThunk`, `Trampoline`, `Free`, `FreeStep`
56//!   - **Fallible Variants:** `TryLazy` (`RcTryLazy`, `ArcTryLazy`), `TryThunk`, `TrySendThunk`, `TryTrampoline`
57//!   - **Generic Containers:** `Identity`, `Pair`, `CatList`
58//!   - **Function Wrappers:** `Endofunction`, `Endomorphism`
59//!   - **Marker Types:** `RcBrand`, `ArcBrand`, `FnBrand`
60//!
61//! ## How it Works
62//!
63//! ### Higher-Kinded Types (HKT)
64//!
65//! Since Rust doesn't support HKTs directly (i.e., it's not possible to use `Option` in `impl Functor for Option`, instead of `Option<T>`), this library uses **Lightweight Higher-Kinded Polymorphism** (also known as the "Brand" pattern or type-level defunctionalization).
66//!
67//! Each type constructor has a corresponding `Brand` type (e.g., `OptionBrand` for `Option`). These brands implement the `Kind` traits, which map the brand and generic arguments back to the concrete type. The library provides macros to simplify this process.
68//!
69//! ```rust
70//! use fp_library::{
71//! 	impl_kind,
72//! 	kinds::*,
73//! };
74//!
75//! pub struct OptionBrand;
76//!
77//! impl_kind! {
78//! 	for OptionBrand {
79//! 		type Of<'a, A: 'a>: 'a = Option<A>;
80//! 	}
81//! }
82//! ```
83//!
84//! ### Zero-Cost Abstractions & Uncurried Semantics
85//!
86//! Unlike many functional programming libraries that strictly adhere to curried functions (e.g., `map(f)(fa)`), `fp-library` adopts **uncurried semantics** (e.g., `map(f, fa)`) for its core abstractions.
87//!
88//! **Why?**
89//! Traditional currying in Rust often requires:
90//!
91//! - Creating intermediate closures for each partial application.
92//! - Heap-allocating these closures (boxing) or wrapping them in reference counters (`Rc`/`Arc`) to satisfy type system constraints.
93//! - Dynamic dispatch (`dyn Fn`), which inhibits compiler optimizations like inlining.
94//!
95//! By using uncurried functions with `impl Fn` or generic bounds, `fp-library` achieves **zero-cost abstractions**:
96//!
97//! - **No Heap Allocation:** Operations like `map` and `bind` do not allocate intermediate closures.
98//! - **Static Dispatch:** The compiler can fully monomorphize generic functions, enabling aggressive inlining and optimization.
99//! - **Ownership Friendly:** Better integration with Rust's ownership and borrowing system.
100//!
101//! This approach ensures that using high-level functional abstractions incurs no runtime penalty compared to hand-written imperative code.
102//!
103//! **Exceptions:**
104//! While the library strives for zero-cost abstractions, some operations inherently require dynamic dispatch or heap allocation due to Rust's type system:
105//!
106//! - **Functions as Data:** When functions are stored in data structures (e.g., inside a `Vec` for `Semiapplicative::apply`, or in `Lazy` thunks), they must often be "type-erased" (wrapped in `Rc<dyn Fn>` or `Arc<dyn Fn>`). This is because every closure in Rust has a unique, anonymous type. To store multiple different closures in the same container, or to compose functions dynamically (like in `Endofunction`), they must be coerced to a common trait object.
107//! - **Lazy Evaluation:** The `Lazy` type relies on storing a thunk that can be cloned and evaluated later, which typically requires reference counting and dynamic dispatch.
108//!
109//! For these specific cases, the library provides `Brand` types (like `RcFnBrand` and `ArcFnBrand`) to let you choose the appropriate wrapper (single-threaded vs. thread-safe) while keeping the rest of your code zero-cost. The library uses a unified `Pointer` hierarchy to abstract over these choices.
110//!
111//! ### Lazy Evaluation & Effect System
112//!
113//! Rust is an eagerly evaluated language. To enable functional patterns like deferred execution and safe recursion, `fp-library` provides a granular set of types that let you opt-in to specific behaviors without paying for unnecessary overhead.
114//!
115//! | Type                   | Primary Use Case                                                                                                            | Stack Safe?                  | Memoized? | Lifetimes  | Send?            | HKT Traits                           |
116//! | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :--------------------------- | :-------- | :--------- | :--------------- | :----------------------------------- |
117//! | **`Thunk<'a, A>`**     | **Glue Code & Borrowing.** Lightweight deferred computation. Best for short chains and working with references.             | Partial (`tail_rec_m` only)  | No        | `'a`       | No               | `Functor`, `Applicative`, `Monad`    |
118//! | **`SendThunk<'a, A>`** | **Thread-Safe Glue Code.** Like `Thunk`, but the closure is `Send`. Enables truly lazy `into_arc_lazy()`.                   | No                           | No        | `'a`       | Yes              | No                                   |
119//! | **`Trampoline<A>`**    | **Deep Recursion & Pipelines.** Heavy-duty computation. Uses a trampoline to guarantee stack safety for infinite recursion. | Yes                          | No        | `'static`  | No               | No                                   |
120//! | **`Lazy<'a, A>`**      | **Caching.** Wraps a computation to ensure it runs at most once. `RcLazy` for single-threaded, `ArcLazy` for thread-safe.   | N/A                          | Yes       | `'a`       | Config-dependent | `RefFunctor`, `Foldable`             |
121//!
122//! Each of these has a fallible counterpart that wraps `Result<A, E>` with ergonomic error-handling combinators:
123//!
124//! | Type                         | Primary Use Case                                                                                                    | Stack Safe?                  | Memoized? | Lifetimes  | Send?            | HKT Traits                                                |
125//! | :--------------------------- | :------------------------------------------------------------------------------------------------------------------ | :--------------------------- | :-------- | :--------- | :--------------- | :--------------------------------------------------------- |
126//! | **`TryThunk<'a, A, E>`**     | **Fallible Glue Code.** Lightweight deferred computation that may fail. Best for short chains with error handling.  | Partial (`tail_rec_m` only)  | No        | `'a`       | No               | `Functor`, `Applicative`, `Monad`, `Bifunctor`, `Foldable` |
127//! | **`TrySendThunk<'a, A, E>`** | **Thread-Safe Fallible Glue Code.** Like `TryThunk`, but the closure is `Send`.                                     | No                           | No        | `'a`       | Yes              | No                                                         |
128//! | **`TryTrampoline<A, E>`**    | **Fallible Deep Recursion.** Stack-safe computation that may fail. Uses a trampoline for unlimited recursion depth. | Yes                          | No        | `'static`  | No               | No                                                         |
129//! | **`TryLazy<'a, A, E>`**      | **Fallible Caching.** Computes a `Result` at most once and caches either the success value or error.                | N/A                          | Yes       | `'a`       | Config-dependent | `RefFunctor`, `Foldable`                                   |
130//!
131//! **Config-dependent Send:** `ArcLazy`/`ArcTryLazy` are `Send + Sync`; `RcLazy`/`RcTryLazy` are not.
132//!
133//! #### The "Why" of Multiple Types
134//!
135//! Unlike lazy languages (e.g., Haskell) where the runtime handles everything, Rust requires us to choose our trade-offs:
136//!
137//! 1. **`Thunk` vs `Trampoline`**: `Thunk` is faster and supports borrowing (`&'a T`). Its `tail_rec_m` is stack-safe, but deep `bind` chains will overflow the stack. `Trampoline` guarantees stack safety for all operations via a trampoline (the `Free` monad) but requires types to be `'static`. Note that `!Send` types like `Rc<T>` are fully supported. A key distinction is that `Thunk` implements `Functor`, `Applicative`, and `Monad` directly, making it suitable for generic programming, while `Trampoline` does not.
138//! 2. **`Thunk` vs `SendThunk`**: `Thunk` wraps `Box<dyn FnOnce() -> A + 'a>` and is `!Send`. `SendThunk` wraps `Box<dyn FnOnce() -> A + Send + 'a>` and can cross thread boundaries. Use `SendThunk` when you need truly lazy `into_arc_lazy()` (converting to `ArcLazy` without eager evaluation), or when building deferred computation chains that will be consumed on another thread. `TrySendThunk` is the fallible counterpart.
139//! 3. **Computation vs Caching**: `Thunk` and `Trampoline` describe _computations_ that are not memoized. Each instance is consumed on `.evaluate()` (which takes `self` by value), so the computation runs exactly once per instance, but constructing a new instance re-executes the work. `Lazy`, by contrast, caches the result so that all clones share a single evaluation. If you have an expensive operation (like a DB call), convert it to a `Lazy` to guarantee it runs at most once.
140//!
141//! #### Workflow Example: Expression Evaluator
142//!
143//! A robust pattern is to use `TryTrampoline` for stack-safe, fallible recursion, `TryLazy` to memoize expensive results, and `TryThunk` to create lightweight views.
144//!
145//! Consider an expression evaluator that handles division errors and deep recursion:
146//!
147//! ```rust
148//! use fp_library::types::*;
149//!
150//! #[derive(Clone)]
151//! enum Expr {
152//! 	Val(i32),
153//! 	Add(Box<Expr>, Box<Expr>),
154//! 	Div(Box<Expr>, Box<Expr>),
155//! }
156//!
157//! // 1. Stack-safe recursion with error handling (TryTrampoline)
158//! fn eval(expr: &Expr) -> TryTrampoline<i32, String> {
159//! 	let expr = expr.clone(); // Capture owned data for 'static closure
160//! 	TryTrampoline::defer(move || match expr {
161//! 		Expr::Val(n) => TryTrampoline::ok(n),
162//! 		Expr::Add(lhs, rhs) => eval(&lhs).bind(move |l| eval(&rhs).map(move |r| l + r)),
163//! 		Expr::Div(lhs, rhs) => eval(&lhs).bind(move |l| {
164//! 			eval(&rhs).bind(move |r| {
165//! 				if r == 0 {
166//! 					TryTrampoline::err("Division by zero".to_string())
167//! 				} else {
168//! 					TryTrampoline::ok(l / r)
169//! 				}
170//! 			})
171//! 		}),
172//! 	})
173//! }
174//!
175//! // Usage
176//! fn main() {
177//! 	let expr = Expr::Div(Box::new(Expr::Val(100)), Box::new(Expr::Val(2)));
178//!
179//! 	// 2. Memoize result (TryLazy)
180//! 	// The evaluation runs at most once, even if accessed multiple times.
181//! 	let result = RcTryLazy::new(move || eval(&expr).evaluate());
182//!
183//! 	// 3. Create deferred view (TryThunk)
184//! 	// Borrow the cached result to format it.
185//! 	let view: TryThunk<String, String> = TryThunk::new(|| {
186//! 		let val = result.evaluate().map_err(|e| e.clone())?;
187//! 		Ok(format!("Result: {}", val))
188//! 	});
189//!
190//! 	assert_eq!(view.evaluate(), Ok("Result: 50".to_string()));
191//! }
192//! ```
193//!
194//! ### Thread Safety and Parallelism
195//!
196//! The library provides a parallel trait hierarchy that mirrors the sequential one.
197//! All `par_*` free functions accept plain `impl Fn + Send + Sync` closures: no wrapper
198//! types required. Element types require `A: Send`; closures require `Send + Sync`.
199//!
200//! | Parallel trait | Operations | Supertraits |
201//! |---|---|---|
202//! | `ParFunctor` | `par_map` | `Kind` |
203//! | `ParCompactable` | `par_compact`, `par_separate` | `Kind` |
204//! | `ParFilterable` | `par_filter_map`, `par_filter` | `ParFunctor + ParCompactable` |
205//! | `ParFoldable` | `par_fold_map` | `Kind` |
206//! | `ParFunctorWithIndex` | `par_map_with_index` | `ParFunctor + FunctorWithIndex` |
207//! | `ParFoldableWithIndex` | `par_fold_map_with_index` | `ParFoldable + FoldableWithIndex` |
208//!
209//! `ParFilterable` provides default implementations of `par_filter_map` and `par_filter`
210//! derived from `par_map` + `par_compact`; types can override them for single-pass efficiency.
211//!
212//! - **`SendCloneableFn`**: Extends `CloneableFn` to provide `Send + Sync` function wrappers. Implemented by `ArcFnBrand`.
213//! - **Rayon Support**: When the `rayon` feature is enabled, `par_*` functions use rayon for true parallel execution. Otherwise they fall back to sequential equivalents.
214//!
215//! ```
216//! use fp_library::{
217//! 	brands::*,
218//! 	functions::*,
219//! };
220//!
221//! let v = vec![1, 2, 3, 4, 5];
222//! // Map in parallel (uses rayon if feature is enabled)
223//! let doubled: Vec<i32> = par_map::<VecBrand, _, _>(|x: i32| x * 2, v.clone());
224//! assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
225//! // Compact options in parallel
226//! let opts = vec![Some(1), None, Some(3), None, Some(5)];
227//! let compacted: Vec<i32> = par_compact::<VecBrand, _>(opts);
228//! assert_eq!(compacted, vec![1, 3, 5]);
229//! // Fold in parallel
230//! let result = par_fold_map::<VecBrand, _, _>(|x: i32| x.to_string(), v);
231//! assert_eq!(result, "12345".to_string());
232//! ```
233//!
234//! ## Example: Using `Functor` with `Option`
235//!
236//! ```
237//! use fp_library::{
238//! 	brands::*,
239//! 	functions::*,
240//! };
241//!
242//! let x = Some(5);
243//! // Map a function over the `Option` using the `Functor` type class
244//! let y = map::<OptionBrand, _, _>(|i| i * 2, x);
245//! assert_eq!(y, Some(10));
246//! ```
247//!
248//! ## Example: Monadic Do-Notation with `m_do!`
249//!
250//! The `m_do!` macro provides Haskell/PureScript-style do-notation for flat monadic code.
251//! It desugars `<-` binds into nested [`bind`](functions::bind) calls.
252//!
253//! ```
254//! use fp_library::{brands::*, functions::*};
255//! use fp_macros::m_do;
256//!
257//! let result = m_do!(OptionBrand {
258//! 	x <- Some(5);
259//! 	y <- Some(x + 1);
260//! 	let z = x * y;
261//! 	pure(z)
262//! });
263//! assert_eq!(result, Some(30));
264//!
265//! // Works with any monad brand
266//! let result = m_do!(VecBrand {
267//! 	x <- vec![1, 2];
268//! 	y <- vec![10, 20];
269//! 	pure(x + y)
270//! });
271//! assert_eq!(result, vec![11, 21, 12, 22]);
272//! ```
273//!
274//! ## Crate Features
275//!
276//! - **`rayon`**: Enables true parallel execution for `par_*` functions using the [rayon](https://github.com/rayon-rs/rayon) library. Without this feature, `par_*` functions fall back to sequential equivalents.
277//! - **`serde`**: Enables serialization and deserialization support for pure data types using the [serde](https://github.com/serde-rs/serde) library.
278
279extern crate fp_macros;
280
281pub mod brands;
282pub mod classes;
283pub mod functions;
284pub mod kinds;
285pub mod types;
286pub(crate) mod utils;
287
288pub use fp_macros::*;