pub struct Aequivalentia<S, A, ForwardFn, BackwardFn>
where
ForwardFn: Fn(&S) -> A,
BackwardFn: Fn(&A) -> S,
{
forward_fn: ForwardFn,
backward_fn: BackwardFn,
_phantom: core::marker::PhantomData<fn(&S) -> A>,
}
impl<S, A, ForwardFn, BackwardFn> Clone for Aequivalentia<S, A, ForwardFn, BackwardFn>
where
ForwardFn: Fn(&S) -> A + Clone,
BackwardFn: Fn(&A) -> S + Clone,
{
fn clone(&self) -> Self {
Self {
forward_fn: self.forward_fn.clone(),
backward_fn: self.backward_fn.clone(),
_phantom: core::marker::PhantomData,
}
}
}
impl<S, A, ForwardFn, BackwardFn> Aequivalentia<S, A, ForwardFn, BackwardFn>
where
ForwardFn: Fn(&S) -> A,
BackwardFn: Fn(&A) -> S,
{
#[inline]
pub fn new(forward_fn: ForwardFn, backward_fn: BackwardFn) -> Self {
Self {
forward_fn,
backward_fn,
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn forward(&self, source: &S) -> A {
(self.forward_fn)(source)
}
#[inline]
pub fn backward(&self, target: &A) -> S {
(self.backward_fn)(target)
}
#[inline]
pub fn modify<F>(&self, source: &S, f: F) -> S
where
F: FnOnce(A) -> A,
{
let a = self.forward(source);
self.backward(&f(a))
}
#[inline]
pub fn reverse(&self) -> Aequivalentia<A, S, BackwardFn, ForwardFn>
where
ForwardFn: Clone,
BackwardFn: Clone,
{
Aequivalentia {
forward_fn: self.backward_fn.clone(),
backward_fn: self.forward_fn.clone(),
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn compose<B, ForwardFn2, BackwardFn2>(
&self,
other: &Aequivalentia<A, B, ForwardFn2, BackwardFn2>,
) -> ComposedAequivalentia<S, A, B, ForwardFn, BackwardFn, ForwardFn2, BackwardFn2>
where
ForwardFn: Clone,
BackwardFn: Clone,
ForwardFn2: Fn(&A) -> B + Clone,
BackwardFn2: Fn(&B) -> A + Clone,
{
ComposedAequivalentia {
outer: self.clone(),
inner: other.clone(),
}
}
}
#[derive(Clone)]
pub struct ComposedAequivalentia<S, A, B, ForwardFn1, BackwardFn1, ForwardFn2, BackwardFn2>
where
ForwardFn1: Fn(&S) -> A,
BackwardFn1: Fn(&A) -> S,
ForwardFn2: Fn(&A) -> B,
BackwardFn2: Fn(&B) -> A,
{
outer: Aequivalentia<S, A, ForwardFn1, BackwardFn1>,
inner: Aequivalentia<A, B, ForwardFn2, BackwardFn2>,
}
impl<S, A, B, ForwardFn1, BackwardFn1, ForwardFn2, BackwardFn2>
ComposedAequivalentia<S, A, B, ForwardFn1, BackwardFn1, ForwardFn2, BackwardFn2>
where
ForwardFn1: Fn(&S) -> A,
BackwardFn1: Fn(&A) -> S,
ForwardFn2: Fn(&A) -> B,
BackwardFn2: Fn(&B) -> A,
{
#[inline]
pub fn forward(&self, source: &S) -> B {
let a = self.outer.forward(source);
self.inner.forward(&a)
}
#[inline]
pub fn backward(&self, target: &B) -> S {
let a = self.inner.backward(target);
self.outer.backward(&a)
}
#[inline]
pub fn modify<F>(&self, source: &S, f: F) -> S
where
F: FnOnce(B) -> B,
{
let b = self.forward(source);
self.backward(&f(b))
}
}
#[inline]
pub fn aequivalentia<S, A, ForwardFn, BackwardFn>(
forward_fn: ForwardFn,
backward_fn: BackwardFn,
) -> Aequivalentia<S, A, ForwardFn, BackwardFn>
where
ForwardFn: Fn(&S) -> A,
BackwardFn: Fn(&A) -> S,
{
Aequivalentia::new(forward_fn, backward_fn)
}
pub struct AequivalentiaRef<S, A, ForwardFn>
where
ForwardFn: Fn(&S) -> &A,
{
forward_fn: ForwardFn,
_phantom: core::marker::PhantomData<fn(&S) -> &A>,
}
impl<S, A, ForwardFn> Clone for AequivalentiaRef<S, A, ForwardFn>
where
ForwardFn: Fn(&S) -> &A + Clone,
{
#[inline]
fn clone(&self) -> Self {
Self {
forward_fn: self.forward_fn.clone(),
_phantom: core::marker::PhantomData,
}
}
}
impl<S, A, ForwardFn> AequivalentiaRef<S, A, ForwardFn>
where
ForwardFn: Fn(&S) -> &A,
{
#[inline]
pub fn new(forward_fn: ForwardFn) -> Self {
Self {
forward_fn,
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn forward<'a>(&self, source: &'a S) -> &'a A {
(self.forward_fn)(source)
}
}
#[inline]
pub fn identitas<T: Clone>() -> Aequivalentia<T, T, impl Fn(&T) -> T, impl Fn(&T) -> T> {
aequivalentia(|t: &T| t.clone(), |t: &T| t.clone())
}
pub type PermutatioAequivalentia<A, B> =
Aequivalentia<(A, B), (B, A), fn(&(A, B)) -> (B, A), fn(&(B, A)) -> (A, B)>;
#[inline]
pub fn permutatio<A: Clone, B: Clone>() -> PermutatioAequivalentia<A, B> {
aequivalentia(
|(a, b): &(A, B)| (b.clone(), a.clone()),
|(b, a): &(B, A)| (a.clone(), b.clone()),
)
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::string::{String, ToString};
#[derive(Clone, Debug, PartialEq)]
struct Celsius(f64);
#[derive(Clone, Debug, PartialEq)]
struct Fahrenheit(f64);
#[derive(Clone, Debug, PartialEq)]
struct UserId(u64);
#[test]
fn test_aequivalentia_basic() {
let celsius_fahrenheit = aequivalentia(
|c: &Celsius| Fahrenheit(c.0 * 9.0 / 5.0 + 32.0),
|f: &Fahrenheit| Celsius((f.0 - 32.0) * 5.0 / 9.0),
);
let freezing_c = Celsius(0.0);
let freezing_f = celsius_fahrenheit.forward(&freezing_c);
assert!((freezing_f.0 - 32.0).abs() < 0.001);
let back = celsius_fahrenheit.backward(&freezing_f);
assert!((back.0 - 0.0).abs() < 0.001);
}
#[test]
fn test_aequivalentia_roundtrip() {
let user_id_aeq = aequivalentia(|id: &UserId| id.0, |n: &u64| UserId(*n));
let original = UserId(12345);
let forward = user_id_aeq.forward(&original);
let roundtrip = user_id_aeq.backward(&forward);
assert_eq!(roundtrip, original);
let n = 67890u64;
let backward = user_id_aeq.backward(&n);
let roundtrip2 = user_id_aeq.forward(&backward);
assert_eq!(roundtrip2, n);
}
#[test]
fn test_aequivalentia_modify() {
let user_id_aeq = aequivalentia(|id: &UserId| id.0, |n: &u64| UserId(*n));
let id = UserId(100);
let incremented = user_id_aeq.modify(&id, |n| n + 1);
assert_eq!(incremented, UserId(101));
}
#[test]
fn test_aequivalentia_reverse() {
let user_id_aeq = aequivalentia(|id: &UserId| id.0, |n: &u64| UserId(*n));
let reversed = user_id_aeq.reverse();
let n = 42u64;
assert_eq!(reversed.forward(&n), UserId(42));
assert_eq!(reversed.backward(&UserId(42)), 42);
}
#[test]
fn test_aequivalentia_composition() {
let unwrap = aequivalentia(|id: &UserId| id.0, |n: &u64| UserId(*n));
let to_string_aeq = aequivalentia(
|n: &u64| n.to_string(),
|s: &String| s.parse::<u64>().unwrap_or(0),
);
let composed = unwrap.compose(&to_string_aeq);
let id = UserId(42);
assert_eq!(composed.forward(&id), "42".to_string());
assert_eq!(composed.backward(&"42".to_string()), UserId(42));
}
#[test]
fn test_identitas() {
let id_aeq = identitas::<i32>();
assert_eq!(id_aeq.forward(&42), 42);
assert_eq!(id_aeq.backward(&42), 42);
}
#[test]
fn test_permutatio() {
let swap_aeq = permutatio::<i32, String>();
let pair = (42, "hello".to_string());
let swapped = swap_aeq.forward(&pair);
assert_eq!(swapped, ("hello".to_string(), 42));
let back = swap_aeq.backward(&swapped);
assert_eq!(back, pair);
}
#[test]
fn test_aequivalentia_ref() {
#[derive(Clone)]
struct Wrapper(String);
let aeq_ref = AequivalentiaRef::new(|w: &Wrapper| &w.0);
let wrapper = Wrapper("hello".to_string());
assert_eq!(aeq_ref.forward(&wrapper), "hello");
}
}