fp_library/lib.rs
1//! A functional programming library for Rust featuring your favourite higher-kinded types and type classes.
2//!
3//! ## Motivation
4//!
5//! 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`).
6//!
7//! `fp-library` aims to bridge this gap by providing:
8//!
9//! 1. A robust encoding of HKTs in stable Rust.
10//! 2. A comprehensive set of standard type classes (`Functor`, `Monad`, `Traversable`, etc.).
11//! 3. Zero-cost abstractions that respect Rust's performance characteristics.
12//!
13//! ## Features
14//!
15//! - **Higher-Kinded Types (HKT):** Implemented using lightweight higher-kinded polymorphism (type-level defunctionalization/brands).
16//! - **Macros:** Procedural macros (`def_kind!`, `impl_kind!`, `Apply!`) to simplify HKT boilerplate and type application.
17//! - **Type Classes:** A comprehensive collection of standard type classes including:
18//! - `Functor`, `Applicative`, `Monad`
19//! - `Semigroup`, `Monoid`
20//! - `Foldable`, `Traversable`
21//! - `Compactable`, `Filterable`, `Witherable`
22//! - `Category`, `Semigroupoid`
23//! - `Pointed`, `Lift`
24//! - `ApplyFirst`, `ApplySecond`, `Semiapplicative`, `Semimonad`
25//! - `MonadRec`, `RefFunctor`
26//! - `Function`, `CloneableFn`, `SendCloneableFn`, `ParFoldable` (Function wrappers and thread-safe operations)
27//! - `Pointer`, `RefCountedPointer`, `SendRefCountedPointer` (Pointer abstraction)
28//! - `Defer`, `SendDefer`
29//! - **Helper Functions:** Standard FP utilities:
30//! - `compose`, `constant`, `flip`, `identity`
31//! - **Data Types:** Implementations for standard and custom types:
32//! - `Option`, `Result`, `Vec`, `String`
33//! - `Identity`, `Lazy`, `Pair`
34//! - `Trampoline`, `Thunk`, `Free`
35//! - `Endofunction`, `Endomorphism`, `SendEndofunction`
36//! - `RcBrand`, `ArcBrand`, `FnBrand`
37//!
38//! ## How it Works
39//!
40//! ### Higher-Kinded Types (HKT)
41//!
42//! Since Rust doesn't support HKTs directly (e.g., `trait Functor<F<_>>`), this library uses **Lightweight Higher-Kinded Polymorphism** (also known as the "Brand" pattern or type-level defunctionalization).
43//!
44//! 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.
45//!
46//! ```rust
47//! use fp_library::{impl_kind, kinds::*};
48//!
49//! pub struct OptionBrand;
50//!
51//! impl_kind! {
52//! for OptionBrand {
53//! type Of<'a, A: 'a>: 'a = Option<A>;
54//! }
55//! }
56//! ```
57//!
58//! ### Zero-Cost Abstractions & Uncurried Semantics
59//!
60//! 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.
61//!
62//! **Why?**
63//! Traditional currying in Rust often requires:
64//!
65//! - Creating intermediate closures for each partial application.
66//! - Heap-allocating these closures (boxing) or wrapping them in reference counters (`Rc`/`Arc`) to satisfy type system constraints.
67//! - Dynamic dispatch (`dyn Fn`), which inhibits compiler optimizations like inlining.
68//!
69//! By using uncurried functions with `impl Fn` or generic bounds, `fp-library` achieves **zero-cost abstractions**:
70//!
71//! - **No Heap Allocation:** Operations like `map` and `bind` do not allocate intermediate closures.
72//! - **Static Dispatch:** The compiler can fully monomorphize generic functions, enabling aggressive inlining and optimization.
73//! - **Ownership Friendly:** Better integration with Rust's ownership and borrowing system.
74//!
75//! This approach ensures that using high-level functional abstractions incurs no runtime penalty compared to hand-written imperative code.
76//!
77//! **Exceptions:**
78//! While the library strives for zero-cost abstractions, some operations inherently require dynamic dispatch or heap allocation due to Rust's type system:
79//!
80//! - **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.
81//! - **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.
82//!
83//! 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.
84//!
85//! ### Lazy Evaluation & Effect System
86//!
87//! 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.
88//!
89//! | Type | Primary Use Case | Stack Safe? | Memoized? | Lifetimes? | HKT Traits |
90//! | :------------------ | :-------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :-------- | :----------- | :----------------------------------- |
91//! | **`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` |
92//! | **`Trampoline<A>`** | **Deep Recursion & Pipelines.** Heavy-duty computation. Uses a trampoline to guarantee stack safety for infinite recursion. | ✅ Yes | ❌ No | ❌ `'static` | ❌ No |
93//! | **`Lazy<'a, A>`** | **Caching.** Wraps a computation to ensure it runs at most once. | N/A | ✅ Yes | ✅ `'a` | ✅ `RefFunctor` |
94//!
95//! #### The "Why" of Three Types
96//!
97//! Unlike lazy languages (e.g., Haskell) where the runtime handles everything, Rust requires us to choose our trade-offs:
98//!
99//! 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.
100//! 2. **Computation vs Caching**: `Thunk` and `Trampoline` describe _computations_—they re-run every time you call `.run()`. If you have an expensive operation (like a DB call), convert it to a `Lazy` to cache the result.
101//!
102//! #### Workflow Example
103//!
104//! A common pattern is to use `Trampoline` for the heavy lifting (recursion), `Lazy` to freeze the result, and `Thunk` to borrow it later.
105//!
106//! ```rust
107//! use fp_library::types::*;
108//!
109//! // 1. Use Trampoline for stack-safe recursion
110//! let heavy_computation = Trampoline::tail_rec_m(|n| {
111//! if n < 1_000 { Trampoline::pure(Step::Loop(n + 1)) } else { Trampoline::pure(Step::Done(n)) }
112//! }, 0);
113//!
114//! // 2. Convert to Lazy to cache the result (runs once)
115//! let cached = Lazy::<_, RcLazyConfig>::from(heavy_computation);
116//!
117//! // 3. Use Thunk to borrow the cached value without re-running
118//! let view = Thunk::new(|| {
119//! let val = cached.get();
120//! format!("Result: {}", val)
121//! });
122//! ```
123//!
124//! ### Thread Safety and Parallelism
125//!
126//! The library supports thread-safe operations through the `SendCloneableFn` extension trait and parallel folding via `ParFoldable`.
127//!
128//! - **`SendCloneableFn`**: Extends `CloneableFn` to provide `Send + Sync` function wrappers. Implemented by `ArcFnBrand`.
129//! - **`ParFoldable`**: Provides `par_fold_map` and `par_fold_right` for parallel execution.
130//! - **Rayon Support**: `VecBrand` supports parallel execution using `rayon` when the `rayon` feature is enabled.
131//!
132//! ```
133//! use fp_library::{brands::*, functions::*};
134//!
135//! let v = vec![1, 2, 3, 4, 5];
136//! // Create a thread-safe function wrapper
137//! let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|x: i32| x.to_string());
138//! // Fold in parallel (if rayon feature is enabled)
139//! let result = par_fold_map::<ArcFnBrand, VecBrand, _, _>(f, v);
140//! assert_eq!(result, "12345".to_string());
141//! ```
142//!
143//! ## Example: Using `Functor` with `Option`
144//!
145//! ```
146//! use fp_library::{brands::*, functions::*};
147//!
148//! let x = Some(5);
149//! // Map a function over the `Option` using the `Functor` type class
150//! let y = map::<OptionBrand, _, _, _>(|i| i * 2, x);
151//! assert_eq!(y, Some(10));
152//! ```
153//!
154//! ## Crate Features
155//!
156//! - **`rayon`**: Enables parallel folding operations (`ParFoldable`) and parallel execution support for `VecBrand` using the [rayon](https://github.com/rayon-rs/rayon) library.
157
158extern crate fp_macros;
159
160pub mod brands;
161pub mod classes;
162pub mod functions;
163pub mod kinds;
164pub mod types;
165
166pub use fp_macros::Apply;
167pub use fp_macros::Kind;
168pub use fp_macros::def_kind;
169pub use fp_macros::impl_kind;