diff --git a/src/lenses/lens.rs b/src/lenses/lens.rs new file mode 100644 index 0000000..5828290 --- /dev/null +++ b/src/lenses/lens.rs @@ -0,0 +1,46 @@ +use crate::{ + lenses::{LensOver, LensView}, + Optics, OpticsTrait, +}; + +type Getter = dyn Fn(T) -> U; +type Setter = dyn Fn(T, U) -> T; + +pub struct LensInner(pub(crate) Box>, pub(crate) Box>); +impl OpticsTrait for LensInner {} + +impl LensView for LensInner { + type Field = U; + + fn view(&self, thing: T) -> Self::Field { + (self.0)(thing) + } +} +impl LensOver for LensInner { + fn over(&self, thing: T, f: F) -> T + where + F: FnOnce(Self::Field) -> Self::Field, + { + let v = f((self.0)(thing.clone())); + (self.1)(thing, v) + } + + fn set(&self, thing: T, v: Self::Field) -> T { + (self.1)(thing, v) + } +} + +/// Makes a lens that implements `LensView` and `LensOver` with the provided functions +pub fn lens_from_boxed( + getter: Box>, + setter: Box>, +) -> Optics> { + Optics(LensInner(getter, setter)) +} +/// Makes a lens that implements `LensView` and `LensOver` with the provided functions +pub fn lens( + getter: impl Fn(T) -> U + 'static, + setter: impl Fn(T, U) -> T + 'static, +) -> Optics> { + Optics(LensInner(Box::new(getter), Box::new(setter))) +} diff --git a/src/lenses/mod.rs b/src/lenses/mod.rs index 9270b71..7a76145 100644 --- a/src/lenses/mod.rs +++ b/src/lenses/mod.rs @@ -7,6 +7,9 @@ mod second; pub use second::_1; mod to; +pub use to::{to, to_from_boxed}; +mod lens; +pub use lens::{lens, lens_from_boxed}; use crate::{Optics, OpticsTrait}; @@ -161,4 +164,53 @@ mod tests { let res = lens(a, |v| v + 1); assert_eq!(res, ((1, 3), 3)); } + + #[derive(Debug, PartialEq, Clone)] + struct Hello { + hey: u8, + } + + #[test] + fn can_use_to() { + // making a getter + let l = to(|hello: Hello| hello.hey); + + let hello = Hello { hey: 8 }; + assert_eq!(l(hello), 8); + } + + #[test] + fn can_make_lens_out_of_funcs() { + // making a lens + let l = lens( + |hello: Hello| hello.hey, + |mut hello: Hello, v: u8| { + hello.hey = v; + hello + }, + ); + + let hello = Hello { hey: 8 }; + assert_eq!(l(hello), 8); + + let hello = Hello { hey: 8 }; + assert_eq!(l(hello, |v| v + 1), Hello { hey: 9 }); + } + + #[test] + fn can_make_lens_out_of_to() { + // we first use to, and then use that to make a full lens + + let l = to(|hello: Hello| hello.hey); + let l = l.make_lens(|mut hello: Hello, v: u8| { + hello.hey = v; + hello + }); + + let hello = Hello { hey: 8 }; + assert_eq!(l(hello), 8); + + let hello = Hello { hey: 8 }; + assert_eq!(l(hello, |v| v + 1), Hello { hey: 9 }); + } } diff --git a/src/lenses/to.rs b/src/lenses/to.rs new file mode 100644 index 0000000..bce8369 --- /dev/null +++ b/src/lenses/to.rs @@ -0,0 +1,30 @@ +use crate::{lenses::LensView, Optics, OpticsTrait}; + +use super::lens::LensInner; + +pub struct ToInner(Box U>); +impl OpticsTrait for ToInner {} + +impl LensView for ToInner { + type Field = U; + + fn view(&self, thing: T) -> Self::Field { + (self.0)(thing) + } +} + +/// Makes a lens that implements `LensView` with the provided function +pub fn to_from_boxed(f: Box U>) -> Optics> { + Optics(ToInner(f)) +} +/// Makes a lens that implements `LensView` with the provided function +pub fn to(f: impl Fn(T) -> U + 'static) -> Optics> { + Optics(ToInner(Box::new(f))) +} + +impl Optics> { + /// Makes a full lens that implements `LensView` and `LensOver` with the provided functions + pub fn make_lens(self, setter: impl Fn(T, U) -> T + 'static) -> Optics> { + Optics(LensInner((self.0).0, Box::new(setter))) + } +}