pub struct Aspectus<S, A, GetFn, SetFn>
where
GetFn: Fn(&S) -> A,
SetFn: Fn(&S, A) -> S,
{
get_fn: GetFn,
set_fn: SetFn,
_phantom: core::marker::PhantomData<fn(&S) -> A>,
}
impl<S, A, GetFn, SetFn> Clone for Aspectus<S, A, GetFn, SetFn>
where
GetFn: Fn(&S) -> A + Clone,
SetFn: Fn(&S, A) -> S + Clone,
{
fn clone(&self) -> Self {
Self {
get_fn: self.get_fn.clone(),
set_fn: self.set_fn.clone(),
_phantom: core::marker::PhantomData,
}
}
}
impl<S, A, GetFn, SetFn> Aspectus<S, A, GetFn, SetFn>
where
GetFn: Fn(&S) -> A,
SetFn: Fn(&S, A) -> S,
{
#[inline]
pub fn new(get_fn: GetFn, set_fn: SetFn) -> Self {
Self {
get_fn,
set_fn,
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn get(&self, source: &S) -> A {
(self.get_fn)(source)
}
#[inline]
pub fn set(&self, source: &S, value: A) -> S {
(self.set_fn)(source, value)
}
#[inline]
pub fn modify<F>(&self, source: &S, f: F) -> S
where
F: FnOnce(A) -> A,
{
let value = self.get(source);
self.set(source, f(value))
}
#[inline]
pub fn compose<B, GetFn2, SetFn2>(
&self,
other: &Aspectus<A, B, GetFn2, SetFn2>,
) -> ComposedAspectus<S, A, B, GetFn, SetFn, GetFn2, SetFn2>
where
GetFn: Clone,
SetFn: Clone,
GetFn2: Fn(&A) -> B + Clone,
SetFn2: Fn(&A, B) -> A + Clone,
{
ComposedAspectus {
outer: self.clone(),
inner: other.clone(),
}
}
}
#[derive(Clone)]
pub struct ComposedAspectus<S, A, B, GetFn1, SetFn1, GetFn2, SetFn2>
where
GetFn1: Fn(&S) -> A,
SetFn1: Fn(&S, A) -> S,
GetFn2: Fn(&A) -> B,
SetFn2: Fn(&A, B) -> A,
{
outer: Aspectus<S, A, GetFn1, SetFn1>,
inner: Aspectus<A, B, GetFn2, SetFn2>,
}
impl<S, A, B, GetFn1, SetFn1, GetFn2, SetFn2>
ComposedAspectus<S, A, B, GetFn1, SetFn1, GetFn2, SetFn2>
where
GetFn1: Fn(&S) -> A,
SetFn1: Fn(&S, A) -> S,
GetFn2: Fn(&A) -> B,
SetFn2: Fn(&A, B) -> A,
{
#[inline]
pub fn get(&self, source: &S) -> B {
let a = self.outer.get(source);
self.inner.get(&a)
}
#[inline]
pub fn set(&self, source: &S, value: B) -> S {
let a = self.outer.get(source);
let new_a = self.inner.set(&a, value);
self.outer.set(source, new_a)
}
#[inline]
pub fn modify<F>(&self, source: &S, f: F) -> S
where
F: FnOnce(B) -> B,
{
let a = self.outer.get(source);
let b = self.inner.get(&a);
let new_b = f(b);
let new_a = self.inner.set(&a, new_b);
self.outer.set(source, new_a)
}
}
#[inline]
pub fn aspectus<S, A, GetFn, SetFn>(get_fn: GetFn, set_fn: SetFn) -> Aspectus<S, A, GetFn, SetFn>
where
GetFn: Fn(&S) -> A,
SetFn: Fn(&S, A) -> S,
{
Aspectus::new(get_fn, set_fn)
}
pub struct AspectusRef<S, A, GetFn>
where
GetFn: Fn(&S) -> &A,
{
get_fn: GetFn,
_phantom: core::marker::PhantomData<fn(&S) -> &A>,
}
impl<S, A, GetFn> Clone for AspectusRef<S, A, GetFn>
where
GetFn: Fn(&S) -> &A + Clone,
{
#[inline]
fn clone(&self) -> Self {
Self {
get_fn: self.get_fn.clone(),
_phantom: core::marker::PhantomData,
}
}
}
impl<S, A, GetFn> AspectusRef<S, A, GetFn>
where
GetFn: Fn(&S) -> &A,
{
#[inline]
pub fn new(get_fn: GetFn) -> Self {
Self {
get_fn,
_phantom: core::marker::PhantomData,
}
}
#[inline]
pub fn get<'a>(&self, source: &'a S) -> &'a A {
(self.get_fn)(source)
}
#[inline]
pub fn compose<B, GetFn2>(
&self,
other: &AspectusRef<A, B, GetFn2>,
) -> ComposedAspectusRef<S, A, B, GetFn, GetFn2>
where
GetFn: Clone,
GetFn2: Fn(&A) -> &B + Clone,
{
ComposedAspectusRef {
outer: self.clone(),
inner: other.clone(),
}
}
}
pub struct ComposedAspectusRef<S, A, B, GetFn1, GetFn2>
where
GetFn1: Fn(&S) -> &A,
GetFn2: Fn(&A) -> &B,
{
outer: AspectusRef<S, A, GetFn1>,
inner: AspectusRef<A, B, GetFn2>,
}
impl<S, A, B, GetFn1, GetFn2> Clone for ComposedAspectusRef<S, A, B, GetFn1, GetFn2>
where
GetFn1: Fn(&S) -> &A + Clone,
GetFn2: Fn(&A) -> &B + Clone,
{
#[inline]
fn clone(&self) -> Self {
Self {
outer: self.outer.clone(),
inner: self.inner.clone(),
}
}
}
impl<S, A, B, GetFn1, GetFn2> ComposedAspectusRef<S, A, B, GetFn1, GetFn2>
where
GetFn1: Fn(&S) -> &A,
GetFn2: Fn(&A) -> &B,
{
#[inline]
pub fn get<'a>(&self, source: &'a S) -> &'a B
where
A: 'a,
{
self.inner.get(self.outer.get(source))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
#[derive(Clone, Debug, PartialEq)]
struct Line {
start: Point,
end: Point,
}
#[test]
fn test_aspectus_basic() {
let x_aspectus = aspectus(|p: &Point| p.x, |p: &Point, x: i32| Point { x, y: p.y });
let point = Point { x: 10, y: 20 };
assert_eq!(x_aspectus.get(&point), 10);
assert_eq!(x_aspectus.set(&point, 100), Point { x: 100, y: 20 });
assert_eq!(x_aspectus.modify(&point, |x| x + 5), Point { x: 15, y: 20 });
}
#[test]
fn test_aspectus_composition() {
let start_aspectus = aspectus(
|l: &Line| l.start.clone(),
|l: &Line, start: Point| Line {
start,
end: l.end.clone(),
},
);
let x_aspectus = aspectus(|p: &Point| p.x, |p: &Point, x: i32| Point { x, y: p.y });
let start_x = start_aspectus.compose(&x_aspectus);
let line = Line {
start: Point { x: 0, y: 0 },
end: Point { x: 10, y: 10 },
};
assert_eq!(start_x.get(&line), 0);
assert_eq!(
start_x.set(&line, 5),
Line {
start: Point { x: 5, y: 0 },
end: Point { x: 10, y: 10 },
}
);
}
#[test]
fn test_aspectus_ref() {
let x_ref = AspectusRef::new(|p: &Point| &p.x);
let point = Point { x: 10, y: 20 };
assert_eq!(*x_ref.get(&point), 10);
}
#[test]
fn test_aspectus_ref_compose_zero_clone() {
let start_ref = AspectusRef::new(|l: &Line| &l.start);
let x_ref = AspectusRef::new(|p: &Point| &p.x);
let start_x = start_ref.compose(&x_ref);
let line = Line {
start: Point { x: 3, y: 7 },
end: Point { x: 10, y: 10 },
};
let got: &i32 = start_x.get(&line);
assert_eq!(*got, 3);
assert!(core::ptr::eq(got, &raw const line.start.x));
}
#[test]
fn test_aspectus_ref_compose_is_clone() {
let start_ref = AspectusRef::new(|l: &Line| &l.start);
let x_ref = AspectusRef::new(|p: &Point| &p.x);
let start_x = start_ref.compose(&x_ref);
let cloned = start_x.clone();
let line = Line {
start: Point { x: 9, y: 1 },
end: Point { x: 2, y: 2 },
};
assert_eq!(*cloned.get(&line), 9);
}
#[test]
fn test_composed_aspectus_modify() {
let start_aspectus = aspectus(
|l: &Line| l.start.clone(),
|l: &Line, start: Point| Line {
start,
end: l.end.clone(),
},
);
let x_aspectus = aspectus(|p: &Point| p.x, |p: &Point, x: i32| Point { x, y: p.y });
let start_x = start_aspectus.compose(&x_aspectus);
let line = Line {
start: Point { x: 3, y: 7 },
end: Point { x: 10, y: 10 },
};
let result = start_x.modify(&line, |x| x * 2);
assert_eq!(result.start.x, 6, "focused field should be doubled");
assert_eq!(
result.start.y, 7,
"non-focused field in inner struct must be unchanged"
);
assert_eq!(
result.end, line.end,
"non-focused field in outer struct must be unchanged"
);
}
}