# Functional Patterns — `fp.rs`

Inspired by **Laravel's pipeline**, **AdonisJS middleware chaining**, and standard functional programming utilities.

## 7.1 Core Combinators — `fp/compose.rs`

```rust
/// Thread a value through a sequence of transformations
/// Mirrors Laravel's pipeline / AdonisJS middleware chain
pub fn pipe<T>(value: T, fns: Vec<Box<dyn Fn(T) -> T>>) -> T

/// Compose two functions: compose(f, g)(x) = f(g(x))
pub fn compose<A, B, C>(f: impl Fn(B) -> C, g: impl Fn(A) -> B) -> impl Fn(A) -> C

/// Apply a side-effect then return the original value — Laravel: tap()
pub fn tap<T>(value: T, f: impl FnOnce(&T)) -> T

/// Apply function and return result — AdonisJS: apply
pub fn apply<T, U>(value: T, f: impl FnOnce(T) -> U) -> U

/// Return default if None — convenience over Option::unwrap_or_default
pub fn or_default<T: Default>(opt: Option<T>) -> T

/// Retry an operation N times before failing
pub fn retry<T, E>(times: usize, f: impl Fn() -> Result<T, E>) -> Result<T, E>
```

## 7.2 Lazy & Memoization — `fp/lazy.rs`

```rust
use once_cell::sync::OnceCell;

/// Lazily initialized value — computed only on first access
pub struct Lazy<T> {
    cell: OnceCell<T>,
    init: Box<dyn Fn() -> T + Send + Sync>,
}

impl<T> Lazy<T> {
    pub fn new(init: impl Fn() -> T + Send + Sync + 'static) -> Self
    pub fn get(&self) -> &T   // computes on first call, returns cached after
    pub fn is_initialized(&self) -> bool
}

/// Memoize a single-argument function using a HashMap cache
pub fn memoize<A, R>(f: impl Fn(A) -> R) -> impl FnMut(A) -> R
where
    A: std::hash::Hash + Eq + Clone,
    R: Clone,

/// Run a closure exactly once, cache the result (thread-safe)
/// Wraps `once_cell::sync::OnceCell`
pub fn once<T>(f: impl FnOnce() -> T) -> impl Fn() -> T
```

## 7.3 Helper Macros — `fp/macros.rs`

```rust
/// Build a Vec of boxed closures for use with pipe()
macro_rules! pipeline {
    ($($fn:expr),* $(,)?) => {
        vec![$(Box::new($fn) as Box<dyn Fn(_) -> _>),*]
    };
}

/// Unwrap or return Err with a RokError variant
macro_rules! require {
    ($opt:expr, $err:expr) => {
        match $opt {
            Some(v) => v,
            None => return Err($err),
        }
    };
}

/// Conditional expression returning a value
macro_rules! when {
    ($cond:expr => $then:expr, else $else:expr) => {
        if $cond { $then } else { $else }
    };
}
```