pub fn identity<A>(a: A) -> A {
a
}
pub fn const_<A: Clone>(value: A) -> impl Fn(()) -> A + Clone {
move |_| value.clone()
}
pub fn always<A: Clone, B>(value: A) -> impl Fn(B) -> A + Clone {
move |_| value.clone()
}
pub fn flip<A, B, C>(f: impl Fn(A, B) -> C) -> impl Fn(B, A) -> C {
move |b, a| f(a, b)
}
pub fn compose<A, B, C>(f: impl Fn(B) -> C, g: impl Fn(A) -> B) -> impl Fn(A) -> C {
move |a| f(g(a))
}
pub fn pipe1<A, B>(a: A, f: impl FnOnce(A) -> B) -> B {
f(a)
}
pub fn pipe2<A, B, C>(a: A, f: impl FnOnce(A) -> B, g: impl FnOnce(B) -> C) -> C {
g(f(a))
}
pub fn pipe3<A, B, C, D>(
a: A,
f: impl FnOnce(A) -> B,
g: impl FnOnce(B) -> C,
h: impl FnOnce(C) -> D,
) -> D {
h(g(f(a)))
}
pub fn absurd<A>(never: std::convert::Infallible) -> A {
match never {}
}
pub fn tupled<A, B, C>(f: impl Fn(A, B) -> C) -> impl Fn((A, B)) -> C {
move |(a, b)| f(a, b)
}
pub fn untupled<A, B, C>(f: impl Fn((A, B)) -> C) -> impl Fn(A, B) -> C {
move |a, b| f((a, b))
}
pub fn memoize<A, B>(f: impl Fn(A) -> B) -> impl FnMut(A) -> B
where
A: std::hash::Hash + Eq + Clone,
B: Clone,
{
let mut cache = std::collections::HashMap::<A, B>::new();
move |a: A| {
if let Some(v) = cache.get(&a) {
return v.clone();
}
let v = f(a.clone());
cache.insert(a, v.clone());
v
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
mod identity_tests {
use super::*;
#[rstest]
#[case::integer(42_i32)]
#[case::zero(0_i32)]
#[case::negative(-7_i32)]
fn identity_returns_input(#[case] v: i32) {
assert_eq!(identity(v), v);
}
#[test]
fn identity_works_for_strings() {
let s = "hello".to_string();
assert_eq!(identity(s.clone()), s);
}
#[test]
fn identity_works_for_options() {
assert_eq!(identity(Some(1_i32)), Some(1));
}
}
mod const_tests {
use super::*;
#[test]
fn const_always_returns_same_value() {
let f = const_(99_i32);
assert_eq!(f(()), 99);
assert_eq!(f(()), 99);
}
#[test]
fn always_ignores_argument() {
let f = always::<bool, i32>(true);
assert!(f(0));
assert!(f(42));
}
#[test]
fn always_with_string_value() {
let f = always::<&str, i32>("fixed");
assert_eq!(f(100), "fixed");
}
}
mod flip_tests {
use super::*;
#[test]
fn flip_reverses_arguments() {
let sub = |a: i32, b: i32| a - b;
let flipped = flip(sub);
assert_eq!(flipped(3, 10), 10 - 3);
}
#[test]
fn flip_of_flip_is_original() {
let f = |a: i32, b: i32| a * 10 + b;
let result = flip(flip(|a: i32, b: i32| a * 10 + b))(1, 2);
assert_eq!(result, f(1, 2));
}
#[rstest]
#[case(10_i32, 3_i32, -7_i32)]
#[case(5_i32, 5_i32, 0_i32)]
#[case(0_i32, 1_i32, 1_i32)]
fn flip_sub(#[case] b: i32, #[case] a: i32, #[case] expected: i32) {
let flipped_sub = flip(|a: i32, b: i32| a - b);
assert_eq!(flipped_sub(b, a), expected);
}
}
mod compose_tests {
use super::*;
#[test]
fn compose_applies_right_then_left() {
let add_one = |n: i32| n + 1;
let double = |n: i32| n * 2;
let composed = compose(add_one, double);
assert_eq!(composed(3), 7); }
#[test]
fn compose_with_identity_is_identity() {
let double = |n: i32| n * 2;
let composed = compose(identity, double);
assert_eq!(composed(5), 10);
}
#[rstest]
#[case(0_i32, 1_i32)]
#[case(1_i32, 3_i32)]
#[case(2_i32, 5_i32)]
fn compose_double_plus_one(#[case] input: i32, #[case] expected: i32) {
let f = compose(|n: i32| n + 1, |n: i32| n * 2);
assert_eq!(f(input), expected);
}
}
mod pipe_tests {
use super::*;
#[test]
fn pipe1_applies_single_function() {
assert_eq!(pipe1(5_i32, |n| n * 3), 15);
}
#[test]
fn pipe2_applies_two_functions_left_to_right() {
assert_eq!(pipe2(3_i32, |n| n * 2, |n| n + 1), 7);
}
#[test]
fn pipe3_applies_three_functions_left_to_right() {
assert_eq!(
pipe3(2_i32, |n| n + 1, |n| n * 2, |n| n - 1),
5 );
}
#[test]
fn pipe1_with_string_conversion() {
assert_eq!(pipe1(42_i32, |n| n.to_string()), "42");
}
}
mod tupled_tests {
use super::*;
#[test]
fn tupled_converts_two_arg_to_tuple_arg() {
let add = |a: i32, b: i32| a + b;
let tupled_add = tupled(add);
assert_eq!(tupled_add((3, 4)), 7);
}
#[test]
fn untupled_converts_tuple_arg_to_two_arg() {
let sum_pair = |(a, b): (i32, i32)| a + b;
let two_arg = untupled(sum_pair);
assert_eq!(two_arg(3, 4), 7);
}
#[test]
fn tupled_then_untupled_is_original() {
let f = |a: i32, b: i32| a - b;
let roundtrip = untupled(tupled(f));
assert_eq!(roundtrip(10, 3), 7);
}
#[rstest]
#[case(1_i32, 2_i32, 3_i32)]
#[case(0_i32, 0_i32, 0_i32)]
#[case(-1_i32, 1_i32, 0_i32)]
fn tupled_add_cases(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
let f = tupled(|a: i32, b: i32| a + b);
assert_eq!(f((a, b)), expected);
}
}
mod memoize_tests {
use super::*;
#[test]
fn memoize_returns_correct_value() {
let double = memoize(|n: i32| n * 2);
let mut double = double;
assert_eq!(double(5), 10);
}
#[test]
fn memoize_returns_same_value_on_second_call() {
let mut f = memoize(|n: i32| n + 100);
assert_eq!(f(3), 103);
assert_eq!(f(3), 103);
}
#[test]
fn memoize_caches_independently_per_key() {
let mut f = memoize(|s: &str| s.len());
assert_eq!(f("hi"), 2);
assert_eq!(f("hello"), 5);
assert_eq!(f("hi"), 2);
}
}
}