rok-utils 0.2.2

Laravel/AdonisJS-inspired utility helpers for the Rok ecosystem
Documentation
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;

pub fn pipe<T>(value: T, fns: Vec<fn(T) -> T>) -> T {
    fns.into_iter().fold(value, |v, f| f(v))
}

pub fn compose<A, B, C>(f: impl Fn(B) -> C, g: impl Fn(A) -> B) -> impl Fn(A) -> C {
    move |x| f(g(x))
}

pub fn tap<T>(value: T, f: impl FnOnce(&T)) -> T {
    f(&value);
    value
}

pub fn apply<T, U>(value: T, f: impl FnOnce(T) -> U) -> U {
    f(value)
}

pub fn or_default<T: Default>(opt: Option<T>) -> T {
    opt.unwrap_or_default()
}

pub fn retry<T, E>(times: usize, f: impl FnMut() -> Result<T, E>) -> Result<T, E> {
    let mut f = f;
    let mut last_err = None;
    for _ in 0..times {
        match f() {
            ok @ Ok(_) => return ok,
            Err(e) => last_err = Some(e),
        }
    }
    Err(last_err.unwrap())
}

#[cfg(feature = "random")]
pub fn shuffle<T: Clone>(arr: &[T]) -> Vec<T> {
    use rand::seq::SliceRandom;
    let mut rng = rand::thread_rng();
    let mut out: Vec<T> = arr.to_vec();
    out.shuffle(&mut rng);
    out
}

#[derive(Debug)]
pub struct Lazy<T, F = fn() -> T> {
    cell: once_cell::sync::Lazy<T, F>,
}

impl<T, F: FnOnce() -> T> Lazy<T, F> {
    pub fn new(init: F) -> Self {
        Self {
            cell: once_cell::sync::Lazy::new(init),
        }
    }

    pub fn get(&self) -> &T {
        Deref::deref(&self.cell)
    }
}

unsafe impl<T, F: FnOnce() -> T + Send + Sync> Send for Lazy<T, F> {}
unsafe impl<T, F: FnOnce() -> T + Send + Sync> Sync for Lazy<T, F> {}

pub fn memoize<K, R>(f: impl Fn(K) -> R) -> impl Fn(K) -> R
where
    K: Hash + Eq + Clone,
    R: Clone,
{
    let cache = std::cell::RefCell::new(HashMap::<K, R>::new());
    move |arg: K| {
        let arg = arg.clone();
        if let Some(result) = cache.borrow().get(&arg) {
            return result.clone();
        }
        let result = f(arg.clone());
        cache.borrow_mut().insert(arg, result.clone());
        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pipe() {
        let result = pipe(1, vec![|x| x + 1, |x| x * 2]);
        assert_eq!(result, 4);
    }

    #[test]
    fn test_compose() {
        let f = compose(|x: i32| x + 1, |x: i32| x * 2);
        assert_eq!(f(3), 7);
    }

    #[test]
    fn test_tap() {
        let mut seen = false;
        let _ = tap(42, |v| seen = *v == 42);
        assert!(seen);
    }

    #[test]
    fn test_apply() {
        let result = apply(5, |x| x * 2);
        assert_eq!(result, 10);
    }

    #[test]
    fn test_or_default() {
        assert_eq!(or_default(Some(42)), 42);
        assert_eq!(or_default(None::<i32>), 0);
    }

    #[test]
    fn test_retry_success() {
        let result = retry(3, || Ok::<i32, i32>(42));
        assert_eq!(result.unwrap(), 42);
    }

    #[test]
    fn test_retry_eventual() {
        let mut attempts = 0;
        let result = retry(3, &mut || {
            attempts += 1;
            if attempts < 3 {
                Err(1)
            } else {
                Ok(42)
            }
        });
        assert_eq!(result.unwrap(), 42);
        assert_eq!(attempts, 3);
    }

    #[test]
    fn test_retry_failure() {
        let result = retry(2, || Err::<i32, i32>(42));
        assert!(result.is_err());
    }

    #[test]
    fn test_lazy() {
        let lazy: Lazy<i32, fn() -> i32> = Lazy::new(|| 42);
        assert_eq!(*lazy.get(), 42);
        assert_eq!(*lazy.get(), 42);
    }

    #[test]
    fn test_memoize() {
        let count_ptr = std::cell::Cell::new(0);
        let memoized = memoize(move |x: i32| {
            count_ptr.set(count_ptr.get() + 1);
            x * 2
        });
        assert_eq!(memoized(5), 10);
        assert_eq!(memoized(5), 10);
        let _ = count_ptr;
    }
}