use crate::kernel::Effect;
pub struct EnvLens<S, A> {
project: Box<dyn FnOnce(&mut S) -> A + 'static>,
}
impl<S: 'static, A: 'static> EnvLens<S, A> {
#[inline]
pub fn new<F>(f: F) -> Self
where
F: FnOnce(&mut S) -> A + 'static,
{
Self {
project: Box::new(f),
}
}
#[inline]
pub fn get(self, s: &mut S) -> A {
(self.project)(s)
}
#[inline]
pub fn compose<B: 'static>(self, other: EnvLens<A, B>) -> EnvLens<S, B> {
EnvLens::new(move |s: &mut S| {
let mut a = (self.project)(s);
other.get(&mut a)
})
}
}
#[inline]
pub fn identity_lens<S: Clone + 'static>() -> EnvLens<S, S> {
EnvLens::new(|s: &mut S| s.clone())
}
#[inline]
pub fn focus<S, A, Err, Val>(lens: EnvLens<S, A>, eff: Effect<Val, Err, A>) -> Effect<Val, Err, S>
where
S: 'static,
A: 'static,
Err: 'static,
Val: 'static,
{
eff.zoom_env(move |s: &mut S| lens.get(s))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::effect::Effect;
use crate::runtime::run_blocking;
use rstest::rstest;
#[derive(Clone, Debug)]
struct App {
multiplier: i32,
offset: i32,
}
fn app(m: i32, o: i32) -> App {
App {
multiplier: m,
offset: o,
}
}
fn multiplier_lens() -> EnvLens<App, i32> {
EnvLens::new(|a: &mut App| a.multiplier)
}
fn offset_lens() -> EnvLens<App, i32> {
EnvLens::new(|a: &mut App| a.offset)
}
fn double_inner() -> Effect<i32, (), i32> {
Effect::new(|n: &mut i32| Ok(*n * 2))
}
mod env_lens_get {
use super::*;
#[rstest]
#[case::mult(7, 3, 7)]
#[case::off(7, 3, 3)]
fn get_projects_the_correct_field(#[case] m: i32, #[case] o: i32, #[case] _expected: i32) {
let mut a = app(m, o);
assert_eq!(multiplier_lens().get(&mut a), m);
let mut a2 = app(m, o);
assert_eq!(offset_lens().get(&mut a2), o);
}
}
mod identity_law {
use super::*;
#[test]
fn identity_lens_returns_value_unchanged() {
let mut n = 42_i32;
assert_eq!(identity_lens::<i32>().get(&mut n), 42);
}
#[test]
fn focus_identity_lens_is_noop() {
let eff: Effect<i32, (), i32> = Effect::new(|n: &mut i32| Ok(*n));
let widened = focus(identity_lens::<i32>(), eff);
assert_eq!(run_blocking(widened, 42), Ok(42));
}
}
mod composition_law {
use super::*;
#[derive(Clone)]
struct Outer {
inner: Inner,
}
#[derive(Clone)]
struct Inner {
value: i32,
}
fn outer_to_inner() -> EnvLens<Outer, Inner> {
EnvLens::new(|o: &mut Outer| o.inner.clone())
}
fn inner_to_val() -> EnvLens<Inner, i32> {
EnvLens::new(|i: &mut Inner| i.value)
}
#[test]
fn composed_lens_projects_transitively() {
let composed = outer_to_inner().compose(inner_to_val());
let mut o = Outer {
inner: Inner { value: 42 },
};
assert_eq!(composed.get(&mut o), 42);
}
#[test]
fn focus_with_composed_lens() {
let eff: Effect<i32, (), i32> = Effect::new(|n: &mut i32| Ok(*n + 1));
let lens = outer_to_inner().compose(inner_to_val());
let outer_eff = focus(lens, eff);
let result = run_blocking(
outer_eff,
Outer {
inner: Inner { value: 41 },
},
);
assert_eq!(result, Ok(42));
}
}
mod focus_fn {
use super::*;
#[test]
fn focus_runs_inner_effect_with_projected_env() {
let outer_eff = focus(multiplier_lens(), double_inner());
assert_eq!(run_blocking(outer_eff, app(21, 0)), Ok(42));
}
#[test]
fn focus_with_different_lenses_independently() {
let r1 = run_blocking(focus(multiplier_lens(), double_inner()), app(21, 0));
let r2 = run_blocking(focus(offset_lens(), double_inner()), app(0, 21));
assert_eq!(r1, Ok(42));
assert_eq!(r2, Ok(42));
}
#[test]
fn focus_equivalent_to_zoom_env() {
let r_focus = run_blocking(focus(multiplier_lens(), double_inner()), app(21, 0));
let r_zoom = run_blocking(
double_inner().zoom_env(|a: &mut App| a.multiplier),
app(21, 0),
);
assert_eq!(r_focus, r_zoom);
}
}
}