chore: Documentation and unit tests for rosenpass_util::functional

This commit is contained in:
Karolin Varner
2024-10-20 21:41:30 +02:00
parent 48e629fff7
commit e4fdfcae08

View File

@@ -1,4 +1,118 @@
pub fn mutating<T, F>(mut v: T, f: F) -> T
//! Syntax sugar & helpers for a functional programming style and method chains
/// Mutate a value; mostly syntactic sugar
///
/// # Examples
///
/// ```
/// use std::borrow::Borrow;
/// use rosenpass_util::functional::{mutating, MutatingExt, sideeffect, SideffectExt, ApplyExt};
/// use rosenpass_util::mem::DiscardResultExt;
///
/// // Say you have a function that takes a mutable reference
/// fn replace<T: Copy + Eq>(slice: &mut [T], targ: T, by: T) {
/// for val in slice.iter_mut() {
/// if *val == targ {
/// *val = by;
/// }
/// }
/// }
///
/// // Or you have some action that you want to perform as a side effect
/// fn count<T: Copy + Eq>(accumulator: &mut usize, slice: &[T], targ: T) {
/// *accumulator += slice.iter()
/// .filter(|e| *e == &targ)
/// .count();
/// }
///
/// // Lets say, you also have a function that actually modifies the value
/// fn rot2<const N : usize>(slice: [u8; N]) -> [u8; N] {
/// let it = slice.iter()
/// .cycle()
/// .skip(2)
/// .take(N);
///
/// let mut ret = [0u8; N];
/// for (no, elm) in it.enumerate() {
/// ret[no] = *elm;
/// }
///
/// ret
/// }
///
/// // Then these function are kind of clunky to use in an expression;
/// // it can be done, but the resulting code is a bit verbose
/// let mut accu = 0;
/// assert_eq!(b"llo_WorldHe", &{
/// let mut buf = b"Hello World".to_owned();
/// count(&mut accu, &buf, b'l');
/// replace(&mut buf, b' ', b'_');
/// rot2(buf)
/// });
/// assert_eq!(accu, 3);
///
/// // Instead you could use mutating for a slightly prettier syntax,
/// // but this makes only sense if you want to apply a single action
/// assert_eq!(b"Hello_World",
/// &mutating(b"Hello World".to_owned(), |buf|
/// replace(buf, b' ', b'_')));
///
/// // The same is the case for sideeffect()
/// assert_eq!(b"Hello World",
/// &sideeffect(b"Hello World".to_owned(), |buf|
/// count(&mut accu, buf, b'l')));
/// assert_eq!(accu, 6);
///
/// // Calling rot2 on its own is straightforward of course
/// assert_eq!(b"llo WorldHe", &rot2(b"Hello World".to_owned()));
///
/// // These operations can be conveniently used in a method chain
/// // by using the extension traits.
/// //
/// // This is also quite handy if you just need to
/// // modify a value in a long method chain.
/// //
/// // Here apply() also comes in quite handy, because we can use it
/// // to modify the value itself (turning it into a reference).
/// assert_eq!(b"llo_WorldHe",
/// b"Hello World"
/// .to_owned()
/// .sideeffect(|buf| count(&mut accu, buf, b'l'))
/// .mutating(|buf| replace(buf, b' ', b'_'))
/// .apply(rot2)
/// .borrow() as &[u8]);
/// assert_eq!(accu, 9);
///
/// // There is also the mutating_mut variant, which can operate on any mutable reference;
/// // this is mainly useful in a method chain if you are dealing with a mutable reference.
/// //
/// // This example is quite artificial though.
/// assert_eq!(b"llo_WorldHe",
/// b"hello world"
/// .to_owned()
/// .mutating(|buf|
/// // Can not use sideeffect_ref at the start, because it drops the mut reference
/// // status
/// buf.sideeffect_mut(|buf| count(&mut accu, buf, b'l'))
/// .mutating_mut(|buf| replace(buf, b' ', b'_'))
/// .mutating_mut(|buf| replace(buf, b'h', b'H'))
/// .mutating_mut(|buf| replace(buf, b'w', b'W'))
/// // Using rot2 is more complex now
/// .mutating_mut(|buf| {
/// *buf = rot2(*buf);
/// })
/// // Can use sideeffect_ref at the end, because we no longer need
/// // the &mut reference
/// .sideeffect_ref(|buf| count(&mut accu, *buf, b'l'))
/// // And we can use apply to fix the return value if we really want to go
/// // crazy and avoid using a {} block
/// .apply(|_| ())
/// // [crate::mem::DiscardResult::discard_result] does the same job and it is more explicit.
/// .discard_result())
/// .borrow() as &[u8]);
/// assert_eq!(accu, 15);
/// ```
pub fn mutating<T, F>(mut v: T, mut f: F) -> T
where
F: FnMut(&mut T),
{
@@ -6,10 +120,22 @@ where
v
}
/// Mutating values on the fly in a method chain
pub trait MutatingExt {
/// Mutating values on the fly in a method chain (owning)
///
/// # Examples
///
/// See [mutating].
fn mutating<F>(self, f: F) -> Self
where
F: FnMut(&mut Self);
/// Mutating values on the fly in a method chain (non-owning)
///
/// # Examples
///
/// See [mutating].
fn mutating_mut<F>(&mut self, f: F) -> &mut Self
where
F: FnMut(&mut Self);
@@ -32,6 +158,11 @@ impl<T> MutatingExt for T {
}
}
/// Apply a sideeffect using some value in an expression
///
/// # Examples
///
/// See [mutating].
pub fn sideeffect<T, F>(v: T, mut f: F) -> T
where
F: FnMut(&T),
@@ -40,13 +171,29 @@ where
v
}
/// Apply sideeffect on the fly in a method chain
pub trait SideffectExt {
/// Apply sideeffect on the fly in a method chain (owning)
///
/// # Examples
///
/// See [mutating].
fn sideeffect<F>(self, f: F) -> Self
where
F: FnMut(&Self);
/// Apply sideeffect on the fly in a method chain (immutable ref)
///
/// # Examples
///
/// See [mutating].
fn sideeffect_ref<F>(&self, f: F) -> &Self
where
F: FnMut(&Self);
/// Apply sideeffect on the fly in a method chain (mutable ref)
///
/// # Examples
///
/// See [mutating].
fn sideeffect_mut<F>(&mut self, f: F) -> &mut Self
where
F: FnMut(&Self);
@@ -77,11 +224,37 @@ impl<T> SideffectExt for T {
}
}
/// Just run the function
///
/// This is occasionally useful; in particular, you can
/// use it to control the meaning of the question mark operator.
///
/// # Examples
///
/// ```
/// use rosenpass_util::functional::run;
///
/// fn add_and_mul(a: Option<u32>, b: Option<u32>, c: anyhow::Result<u32>, d: anyhow::Result<u32>) -> u32 {
/// run(|| -> anyhow::Result<u32> {
/// let ab = run(|| Some(a? * b?)).unwrap_or(0);
/// Ok(ab + c? + d?)
/// }).unwrap()
/// }
///
/// assert_eq!(98, add_and_mul(Some(10), Some(9), Ok(3), Ok(5)));
/// assert_eq!(8, add_and_mul(None, Some(15), Ok(3), Ok(5)));
/// ```
pub fn run<R, F: FnOnce() -> R>(f: F) -> R {
f()
}
/// Apply a function to a value in a method chain
pub trait ApplyExt: Sized {
/// Apply a function to a value in a method chain
///
/// # Examples
///
/// See [mutating].
fn apply<R, F>(self, f: F) -> R
where
F: FnOnce(Self) -> R;