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