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 (`trait_kind!`, `impl_kind!`, `Apply!`) to simplify HKT boilerplate and type application.
20//! - **Type Classes:** A comprehensive collection of standard type classes including:
21//! - **Core:** `Functor`, `Applicative`, `Monad`, `Semigroup`, `Monoid`, `Foldable`, `Traversable`
22//! - **Bifunctors:** `Bifunctor`, `Bifoldable`, `Bitraversable`
23//! - **Collections:** `Compactable`, `Filterable`, `Witherable`
24//! - **Indexed:** `FunctorWithIndex`, `FoldableWithIndex`, `TraversableWithIndex`
25//! - **Category Theory:** `Category`, `Semigroupoid`, `Profunctor`, `Strong`, `Choice`, `Closed`, `Cochoice`, `Costrong`, `Wander`
26//! - **Utilities:** `Pointed`, `Lift`, `ApplyFirst`, `ApplySecond`, `Semiapplicative`, `Semimonad`, `Contravariant`, `Evaluable`
27//! - **Advanced/Internal:** `MonadRec`, `RefFunctor`, `Deferrable`, `SendDeferrable`
28//! - **Function & Pointer Abstractions:** `Function`, `CloneableFn`, `SendCloneableFn`, `UnsizedCoercible`, `SendUnsizedCoercible`, `ParFoldable`, `Pointer`, `RefCountedPointer`, `SendRefCountedPointer`
29//! - **Optics:** Composable data accessors using profunctor encoding (port of PureScript's `purescript-profunctor-lenses`):
30//! - **Iso / IsoPrime:** Isomorphism between two types
31//! - **Lens / LensPrime:** Focus on a field within a product type
32//! - **Prism / PrismPrime:** Focus on a variant within a sum type
33//! - **AffineTraversal / AffineTraversalPrime:** Optional focusing (combines Lens + Prism)
34//! - **Traversal / TraversalPrime:** Focus on multiple values
35//! - **Getter / GetterPrime:** Read-only access
36//! - **Setter / SetterPrime:** Write-only modification
37//! - **Fold / FoldPrime:** Collecting multiple values (read-only)
38//! - **Review / ReviewPrime:** Constructing values
39//! - **Grate / GratePrime:** Closed/zipping optics
40//! - **Indexed variants:** `IndexedLens`, `IndexedTraversal`, `IndexedGetter`, `IndexedFold`, `IndexedSetter`
41//! - **Composition:** `Composed` struct and `optics_compose` for zero-cost optic composition
42//! - **Helper Functions:** Standard FP utilities:
43//! - `compose`, `constant`, `flip`, `identity`
44//! - **Data Types:** Implementations for standard and custom types:
45//! - **Standard Library:** `Option`, `Result`, `Vec`, `String`
46//! - **Laziness, Memoization & Stack Safety:** `Lazy` (`RcLazy`, `ArcLazy`), `Thunk`, `Trampoline`, `Free`
47//! - **Fallible Variants:** `TryLazy` (`RcTryLazy`, `ArcTryLazy`), `TryThunk`, `TryTrampoline`
48//! - **Generic Containers:** `Identity`, `Pair`, `Step`, `CatList`
49//! - **Function Wrappers:** `Endofunction`, `Endomorphism`, `SendEndofunction`
50//! - **Marker Types:** `RcBrand`, `ArcBrand`, `FnBrand`
51//!
52//! ## How it Works
53//!
54//! ### Higher-Kinded Types (HKT)
55//!
56//! 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).
57//!
58//! 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.
59//!
60//! ```rust
61//! use fp_library::{
62//! impl_kind,
63//! kinds::*,
64//! };
65//!
66//! pub struct OptionBrand;
67//!
68//! impl_kind! {
69//! for OptionBrand {
70//! type Of<'a, A: 'a>: 'a = Option<A>;
71//! }
72//! }
73//! ```
74//!
75//! ### Zero-Cost Abstractions & Uncurried Semantics
76//!
77//! 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.
78//!
79//! **Why?**
80//! Traditional currying in Rust often requires:
81//!
82//! - Creating intermediate closures for each partial application.
83//! - Heap-allocating these closures (boxing) or wrapping them in reference counters (`Rc`/`Arc`) to satisfy type system constraints.
84//! - Dynamic dispatch (`dyn Fn`), which inhibits compiler optimizations like inlining.
85//!
86//! By using uncurried functions with `impl Fn` or generic bounds, `fp-library` achieves **zero-cost abstractions**:
87//!
88//! - **No Heap Allocation:** Operations like `map` and `bind` do not allocate intermediate closures.
89//! - **Static Dispatch:** The compiler can fully monomorphize generic functions, enabling aggressive inlining and optimization.
90//! - **Ownership Friendly:** Better integration with Rust's ownership and borrowing system.
91//!
92//! This approach ensures that using high-level functional abstractions incurs no runtime penalty compared to hand-written imperative code.
93//!
94//! **Exceptions:**
95//! While the library strives for zero-cost abstractions, some operations inherently require dynamic dispatch or heap allocation due to Rust's type system:
96//!
97//! - **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.
98//! - **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.
99//!
100//! 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.
101//!
102//! ### Lazy Evaluation & Effect System
103//!
104//! 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.
105//!
106//! | Type | Primary Use Case | Stack Safe? | Memoized? | Lifetimes? | HKT Traits |
107//! | :------------------ | :-------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :-------- | :----------- | :----------------------------------- |
108//! | **`Thunk<'a, A>`** | **Glue Code & Borrowing.** Lightweight deferred computation. Best for short chains and working with references. | ⚠️ Partial (`tail_rec_m` only) | ❌ No | ✅ `'a` | ✅ `Functor`, `Applicative`, `Monad` |
109//! | **`Trampoline<A>`** | **Deep Recursion & Pipelines.** Heavy-duty computation. Uses a trampoline to guarantee stack safety for infinite recursion. | ✅ Yes | ❌ No | ❌ `'static` | ❌ No |
110//! | **`Lazy<'a, A>`** | **Caching.** Wraps a computation to ensure it runs at most once. | N/A | ✅ Yes | ✅ `'a` | ✅ `RefFunctor` |
111//!
112//! #### The "Why" of Three Types
113//!
114//! Unlike lazy languages (e.g., Haskell) where the runtime handles everything, Rust requires us to choose our trade-offs:
115//!
116//! 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` and `Send`. A key distinction is that `Thunk` implements `Functor`, `Applicative`, and `Monad` directly, making it suitable for generic programming, while `Trampoline` does not.
117//! 2. **Computation vs Caching**: `Thunk` and `Trampoline` describe _computations_: they re-run every time you call `.evaluate()`. If you have an expensive operation (like a DB call), convert it to a `Lazy` to cache the result.
118//!
119//! #### Workflow Example: Expression Evaluator
120//!
121//! A robust pattern is to use `TryTrampoline` for stack-safe, fallible recursion, `TryLazy` to memoize expensive results, and `TryThunk` to create lightweight views.
122//!
123//! Consider an expression evaluator that handles division errors and deep recursion:
124//!
125//! ```rust
126//! use fp_library::types::*;
127//!
128//! #[derive(Clone)]
129//! enum Expr {
130//! Val(i32),
131//! Add(Box<Expr>, Box<Expr>),
132//! Div(Box<Expr>, Box<Expr>),
133//! }
134//!
135//! // 1. Stack-safe recursion with error handling (TryTrampoline)
136//! fn eval(expr: &Expr) -> TryTrampoline<i32, String> {
137//! let expr = expr.clone(); // Capture owned data for 'static closure
138//! TryTrampoline::defer(move || match expr {
139//! Expr::Val(n) => TryTrampoline::ok(n),
140//! Expr::Add(lhs, rhs) => eval(&lhs).bind(move |l| eval(&rhs).map(move |r| l + r)),
141//! Expr::Div(lhs, rhs) => eval(&lhs).bind(move |l| {
142//! eval(&rhs).bind(move |r| {
143//! if r == 0 {
144//! TryTrampoline::err("Division by zero".to_string())
145//! } else {
146//! TryTrampoline::ok(l / r)
147//! }
148//! })
149//! }),
150//! })
151//! }
152//!
153//! // Usage
154//! fn main() {
155//! let expr = Expr::Div(Box::new(Expr::Val(100)), Box::new(Expr::Val(2)));
156//!
157//! // 2. Memoize result (TryLazy)
158//! // The evaluation runs at most once, even if accessed multiple times.
159//! let result = RcTryLazy::new(move || eval(&expr).evaluate());
160//!
161//! // 3. Create deferred view (TryThunk)
162//! // Borrow the cached result to format it.
163//! let view: TryThunk<String, String> = TryThunk::new(|| {
164//! let val = result.evaluate().map_err(|e| e.clone())?;
165//! Ok(format!("Result: {}", val))
166//! });
167//!
168//! assert_eq!(view.evaluate(), Ok("Result: 50".to_string()));
169//! }
170//! ```
171//!
172//! ### Thread Safety and Parallelism
173//!
174//! The library supports thread-safe operations through the `SendCloneableFn` extension trait and parallel folding via `ParFoldable`.
175//!
176//! - **`SendCloneableFn`**: Extends `CloneableFn` to provide `Send + Sync` function wrappers. Implemented by `ArcFnBrand`.
177//! - **`ParFoldable`**: Provides `par_fold_map` and `par_fold_right` for parallel execution.
178//! - **Rayon Support**: `VecBrand` supports parallel execution using `rayon` when the `rayon` feature is enabled.
179//!
180//! ```
181//! use fp_library::{
182//! brands::*,
183//! functions::*,
184//! };
185//!
186//! let v = vec![1, 2, 3, 4, 5];
187//! // Create a thread-safe function wrapper
188//! let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|x: i32| x.to_string());
189//! // Fold in parallel (if rayon feature is enabled)
190//! let result = par_fold_map::<ArcFnBrand, VecBrand, _, _>(f, v);
191//! assert_eq!(result, "12345".to_string());
192//! ```
193//!
194//! ## Example: Using `Functor` with `Option`
195//!
196//! ```
197//! use fp_library::{
198//! brands::*,
199//! functions::*,
200//! };
201//!
202//! let x = Some(5);
203//! // Map a function over the `Option` using the `Functor` type class
204//! let y = map::<OptionBrand, _, _>(|i| i * 2, x);
205//! assert_eq!(y, Some(10));
206//! ```
207//!
208//! ## Crate Features
209//!
210//! - **`rayon`**: Enables parallel folding operations (`ParFoldable`) and parallel execution support for `VecBrand` using the [rayon](https://github.com/rayon-rs/rayon) library.
211//! - **`serde`**: Enables serialization and deserialization support for pure data types using the [serde](https://github.com/serde-rs/serde) library.
212
213extern crate fp_macros;
214
215pub mod brands;
216pub mod classes;
217pub mod functions;
218pub mod kinds;
219pub mod types;
220
221pub use fp_macros::*;