use crate::{
lenses::{Lens, LensOver, LensView},
prisms::{Prism, PrismPreview},
traversals::{Traversal, TraversalOver, TraversalTraverse},
};
use std::ops::Add;
#[derive(Clone, Copy)]
pub struct Combination(A, B);
// additions
// lens + lens
impl const Add> for Lens {
type Output = Lens, Lens>>;
fn add(self, rhs: Lens) -> Self::Output {
Lens(Combination(self, rhs))
}
}
// prism + prism
impl const Add> for Prism {
type Output = Prism, Prism>>;
fn add(self, rhs: Prism) -> Self::Output {
Prism(Combination(self, rhs))
}
}
// traversal + traversal
impl const Add> for Traversal {
type Output = Traversal, Traversal>>;
fn add(self, rhs: Traversal) -> Self::Output {
Traversal(Combination(self, rhs))
}
}
// traversal + lens
impl const Add> for Traversal {
type Output = Traversal, Traversal>>>;
fn add(self, rhs: Lens) -> Self::Output {
Traversal(Combination(self, rhs.to_traversal()))
}
}
// lens + traversal
impl const Add> for Lens {
type Output = Traversal>, Traversal>>;
fn add(self, rhs: Traversal) -> Self::Output {
Traversal(Combination(self.to_traversal(), rhs))
}
}
// traversal + prism
impl const Add> for Traversal {
type Output = Traversal, Traversal>>>;
fn add(self, rhs: Prism) -> Self::Output {
Traversal(Combination(self, rhs.to_traversal()))
}
}
// prism + traversal
impl const Add> for Prism {
type Output = Traversal>, Traversal>>;
fn add(self, rhs: Traversal) -> Self::Output {
Traversal(Combination(self.to_traversal(), rhs))
}
}
// lens + prism
impl const Add> for Lens {
type Output = Traversal>, Traversal>>>;
fn add(self, rhs: Prism) -> Self::Output {
Traversal(Combination(self.to_traversal(), rhs.to_traversal()))
}
}
// prism + traversal
impl const Add> for Prism {
type Output = Traversal>, Traversal>>>;
fn add(self, rhs: Lens) -> Self::Output {
Traversal(Combination(self.to_traversal(), rhs.to_traversal()))
}
}
// trait impls for Combination
// lens + lens
impl LensView for Combination, Lens>
where
A: LensView,
B: LensView,
{
type Field = B::Field;
fn view(&self, thing: T) -> Self::Field {
B::view(&self.1 .0, A::view(&self.0 .0, thing))
}
}
impl LensOver for Combination, Lens>
where
A: LensOver,
B: LensOver,
{
fn over(&self, thing: T, f: F) -> T
where
F: FnOnce(Self::Field) -> Self::Field,
{
A::over(&self.0 .0, thing, |b| B::over(&self.1 .0, b, f))
}
}
// prism + prism
impl PrismPreview for Combination, Prism>
where
A: PrismPreview,
B: PrismPreview,
{
type Field = B::Field;
fn preview(&self, thing: T) -> Option {
A::preview(&self.0 .0, thing).and_then(|a| B::preview(&self.1 .0, a))
}
fn review(&self, thing: Self::Field) -> T {
A::review(&self.0 .0, B::review(&self.1 .0, thing))
}
}
// traversal + traversal
// lens + traversal
// traversal + lens
// prism + traversal
// traversal + prism
// prism + lens
// lens + prism
impl TraversalTraverse for Combination, Traversal>
where
A: TraversalTraverse,
B: TraversalTraverse,
{
type Field = B::Field;
fn traverse(&self, thing: T) -> Vec {
let a = A::traverse(&self.0 .0, thing);
a.into_iter()
.map(|v| B::traverse(&self.1 .0, v))
.flatten()
.collect()
}
}
impl TraversalOver for Combination, Traversal>
where
A: TraversalOver,
B: TraversalOver,
{
fn over(&self, thing: T, mut f: F) -> T
where
F: FnMut(Self::Field) -> Self::Field,
{
A::over(&self.0 .0, thing, |b| B::over(&self.1 .0, b, &mut f))
}
}
#[cfg(test)]
mod tests {
use crate::{
lenses::{_0, _1},
prisms::_Some,
traversals::each,
};
#[test]
fn can_view_lens_combination() {
let a = ((1, 2), 3);
let lens = _0 + _1;
let a = lens(a);
assert_eq!(a, 2);
}
#[test]
fn can_over_lens_combination() {
let a = ((1, 2), 3);
let lens = _0 + _1;
let a = lens(a, |v| v + 1);
assert_eq!(a, ((1, 3), 3));
}
#[test]
fn can_combine_prisms() {
let thing = Some(Some(3));
// combine two traversals
let res = (_Some + _Some)(thing, |v| v + 1);
assert_eq!(res, Some(Some(4)));
}
#[test]
fn can_combine_traversals() {
let array = [vec![1, 2], vec![3, 4]];
// combine two traversals
let res = (each + each)(array, |v| v + 1);
assert_eq!(res, [vec![2, 3], vec![4, 5]]);
}
#[test]
fn can_combine_traversal_with_lens() {
let array = [(1, 2), (3, 4), (5, 6)];
// combine a traversal with a lens
let t = each + _0;
// traverse
let res = t(array);
assert_eq!(res, vec![1, 3, 5]);
// over
let res = t(array, |v| v + 1);
assert_eq!(res, [(2, 2), (4, 4), (6, 6)]);
}
#[test]
fn can_combine_lens_with_traversal() {
let array = [(1, 2), (3, 4), (5, 6)];
// combine a traversal with a lens
let t = _0 + each;
// traverse
let res = t(array);
assert_eq!(res, vec![1, 2]);
// over
let res = t(array, |v| v + 1);
assert_eq!(res, [(2, 3), (3, 4), (5, 6)]);
}
#[test]
fn can_combine_prism_with_traversal() {
let array = [Some(1), None, Some(3), None, Some(5)];
// combine a traversal with a lens
let t = each + _Some;
// traverse
let res = t(array);
assert_eq!(res, vec![1, 3, 5]);
// over
let res = t(array, |v| v + 1);
assert_eq!(res, [Some(2), None, Some(4), None, Some(6)]);
}
#[test]
fn can_combine_traversal_with_prism() {
let array = Some([1, 2, 3]);
// combine a traversal with a lens
let t = _Some + each;
// traverse
let res = t(array);
assert_eq!(res, vec![1, 2, 3]);
// over
let res = t(array, |v| v + 1);
assert_eq!(res, Some([2, 3, 4]));
let array: Option<[i32; 3]> = None;
// traverse
let res = t(array);
assert_eq!(res, vec![]);
// over
let res = t(array, |v| v + 1);
assert_eq!(res, None);
}
#[test]
fn can_combine_prism_with_lens() {
let thing = Some((1, 2));
// combine a traversal with a lens
let t = _Some + _0;
// NOTE: combination of a prism and a lens is a traversal
//
// > The optic kind resulting from a composition is the least upper bound (join)
// > of the optic kinds being composed, if it exists.
// > The Join type family computes the least upper bound given two optic kind tags.
// > For example the Join of a Lens and a Prism is an AffineTraversal.
//
// from: https://hackage.haskell.org/package/optics-0.4/docs/Optics.html
// traversal
let res = t(thing);
assert_eq!(res, vec![1]);
// over
let res = t(thing, |v| v + 1);
assert_eq!(res, Some((2, 2)));
}
#[test]
fn can_combine_lens_with_prism() {
let thing = (Some(1), 2);
// combine a traversal with a lens
let t = _0 + _Some;
// NOTE: combination of a lens and a prism is a traversal
// see can_combine_prism_with_lens for more info
// traversal
let res = t(thing);
assert_eq!(res, vec![1]);
// over
let res = t(thing, |v| v + 1);
assert_eq!(res, (Some(2), 2));
}
#[test]
fn can_combine_as_const() {
use crate::lenses::first::_0Inner;
use crate::lenses::Lens;
const LENS: Lens, Lens<_0Inner>>> = _0 + _0;
let thing: ((i32, i32), i32) = ((1, 2), 3);
let r: i32 = LENS(thing);
assert_eq!(r, 1);
}
}