pub struct Divisio<S, A, PreviewFn, ReviewFn>
where
PreviewFn: Fn(&S) -> Option<A>,
ReviewFn: Fn(A) -> S,
{
preview_fn: PreviewFn,
review_fn: ReviewFn,
_phantom: core::marker::PhantomData<fn(&S) -> Option<A>>,
}
impl<S, A, PreviewFn, ReviewFn> Clone for Divisio<S, A, PreviewFn, ReviewFn>
where
PreviewFn: Fn(&S) -> Option<A> + Clone,
ReviewFn: Fn(A) -> S + Clone,
{
fn clone(&self) -> Self {
Self {
preview_fn: self.preview_fn.clone(),
review_fn: self.review_fn.clone(),
_phantom: core::marker::PhantomData,
}
}
}
impl<S, A, PreviewFn, ReviewFn> Divisio<S, A, PreviewFn, ReviewFn>
where
PreviewFn: Fn(&S) -> Option<A>,
ReviewFn: Fn(A) -> S,
{
#[inline]
pub fn new(preview_fn: PreviewFn, review_fn: ReviewFn) -> Self {
Self {
preview_fn,
review_fn,
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn preview(&self, source: &S) -> Option<A> {
(self.preview_fn)(source)
}
#[inline]
pub fn review(&self, value: A) -> S {
(self.review_fn)(value)
}
#[inline]
pub fn modify<F>(&self, source: &S, f: F) -> Option<S>
where
F: FnOnce(A) -> A,
{
self.preview(source).map(|value| self.review(f(value)))
}
#[inline]
pub fn modify_or_identity<F>(&self, source: &S, f: F) -> S
where
F: FnOnce(A) -> A,
S: Clone,
{
self.modify(source, f).unwrap_or_else(|| source.clone())
}
#[inline]
pub fn set_if_matches(&self, source: &S, value: A) -> Option<S> {
if self.preview(source).is_some() {
Some(self.review(value))
} else {
None
}
}
#[inline]
pub fn matches(&self, source: &S) -> bool {
self.preview(source).is_some()
}
#[inline]
pub fn compose<B, PreviewFn2, ReviewFn2>(
&self,
other: &Divisio<A, B, PreviewFn2, ReviewFn2>,
) -> ComposedDivisio<S, A, B, PreviewFn, ReviewFn, PreviewFn2, ReviewFn2>
where
PreviewFn: Clone,
ReviewFn: Clone,
PreviewFn2: Fn(&A) -> Option<B> + Clone,
ReviewFn2: Fn(B) -> A + Clone,
{
ComposedDivisio {
outer: self.clone(),
inner: other.clone(),
}
}
}
#[derive(Clone)]
pub struct ComposedDivisio<S, A, B, PreviewFn1, ReviewFn1, PreviewFn2, ReviewFn2>
where
PreviewFn1: Fn(&S) -> Option<A>,
ReviewFn1: Fn(A) -> S,
PreviewFn2: Fn(&A) -> Option<B>,
ReviewFn2: Fn(B) -> A,
{
outer: Divisio<S, A, PreviewFn1, ReviewFn1>,
inner: Divisio<A, B, PreviewFn2, ReviewFn2>,
}
impl<S, A, B, PreviewFn1, ReviewFn1, PreviewFn2, ReviewFn2>
ComposedDivisio<S, A, B, PreviewFn1, ReviewFn1, PreviewFn2, ReviewFn2>
where
PreviewFn1: Fn(&S) -> Option<A>,
ReviewFn1: Fn(A) -> S,
PreviewFn2: Fn(&A) -> Option<B>,
ReviewFn2: Fn(B) -> A,
{
#[inline]
pub fn preview(&self, source: &S) -> Option<B> {
self.outer
.preview(source)
.and_then(|a| self.inner.preview(&a))
}
#[inline]
pub fn review(&self, value: B) -> S {
self.outer.review(self.inner.review(value))
}
#[inline]
pub fn modify<F>(&self, source: &S, f: F) -> Option<S>
where
F: FnOnce(B) -> B,
{
self.preview(source).map(|value| self.review(f(value)))
}
#[inline]
pub fn matches(&self, source: &S) -> bool {
self.preview(source).is_some()
}
}
#[inline]
pub fn divisio<S, A, PreviewFn, ReviewFn>(
preview_fn: PreviewFn,
review_fn: ReviewFn,
) -> Divisio<S, A, PreviewFn, ReviewFn>
where
PreviewFn: Fn(&S) -> Option<A>,
ReviewFn: Fn(A) -> S,
{
Divisio::new(preview_fn, review_fn)
}
pub struct DivisioRef<S, A, PreviewFn>
where
PreviewFn: Fn(&S) -> Option<&A>,
{
preview_fn: PreviewFn,
_phantom: core::marker::PhantomData<fn(&S) -> Option<&A>>,
}
impl<S, A, PreviewFn> Clone for DivisioRef<S, A, PreviewFn>
where
PreviewFn: Fn(&S) -> Option<&A> + Clone,
{
#[inline]
fn clone(&self) -> Self {
Self {
preview_fn: self.preview_fn.clone(),
_phantom: core::marker::PhantomData,
}
}
}
impl<S, A, PreviewFn> DivisioRef<S, A, PreviewFn>
where
PreviewFn: Fn(&S) -> Option<&A>,
{
#[inline]
pub fn new(preview_fn: PreviewFn) -> Self {
Self {
preview_fn,
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn preview<'a>(&self, source: &'a S) -> Option<&'a A> {
(self.preview_fn)(source)
}
#[inline]
pub fn matches(&self, source: &S) -> bool {
self.preview(source).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::string::{String, ToString};
#[derive(Clone, Debug, PartialEq)]
enum Either<L, R> {
Left(L),
Right(R),
}
#[derive(Clone, Debug, PartialEq)]
enum Nested {
A(Either<i32, String>),
B,
}
#[test]
fn test_divisio_basic() {
let left_divisio: Divisio<Either<i32, String>, i32, _, _> = divisio(
|e| match e {
Either::Left(l) => Some(*l),
_ => None,
},
Either::Left,
);
let left: Either<i32, String> = Either::Left(42);
let right: Either<i32, String> = Either::Right("hello".to_string());
assert_eq!(left_divisio.preview(&left), Some(42));
assert_eq!(left_divisio.preview(&right), None);
assert_eq!(left_divisio.review(100), Either::Left(100));
}
#[test]
fn test_divisio_modify() {
let left_divisio: Divisio<Either<i32, String>, i32, _, _> = divisio(
|e| match e {
Either::Left(l) => Some(*l),
_ => None,
},
Either::Left,
);
let left: Either<i32, String> = Either::Left(42);
let right: Either<i32, String> = Either::Right("hello".to_string());
assert_eq!(
left_divisio.modify(&left, |x| x * 2),
Some(Either::Left(84))
);
assert_eq!(left_divisio.modify(&right, |x| x * 2), None);
}
#[test]
fn test_divisio_composition() {
let a_divisio: Divisio<Nested, Either<i32, String>, _, _> = divisio(
|n| match n {
Nested::A(e) => Some(e.clone()),
_ => None,
},
Nested::A,
);
let left_divisio: Divisio<Either<i32, String>, i32, _, _> = divisio(
|e| match e {
Either::Left(l) => Some(*l),
_ => None,
},
Either::Left,
);
let composed = a_divisio.compose(&left_divisio);
let nested_left = Nested::A(Either::Left(42));
let nested_right = Nested::A(Either::Right("hello".to_string()));
let nested_b = Nested::B;
assert_eq!(composed.preview(&nested_left), Some(42));
assert_eq!(composed.preview(&nested_right), None);
assert_eq!(composed.preview(&nested_b), None);
assert_eq!(composed.review(100), Nested::A(Either::Left(100)));
}
#[test]
fn test_divisio_ref() {
let left_ref: DivisioRef<Either<i32, String>, i32, _> = DivisioRef::new(|e| match e {
Either::Left(l) => Some(l),
_ => None,
});
let left: Either<i32, String> = Either::Left(42);
let right: Either<i32, String> = Either::Right("hello".to_string());
assert_eq!(left_ref.preview(&left), Some(&42));
assert_eq!(left_ref.preview(&right), None);
assert!(left_ref.matches(&left));
assert!(!left_ref.matches(&right));
}
}