Skip to main content

karpal_optics/
setter.rs

1use crate::optic::Optic;
2
3/// A modify-only optic. Always uses boxed closures since setters are
4/// typically derived from composition or conversion.
5///
6/// `S` — source, `T` — modified source, `A` — focus, `B` — replacement.
7pub struct Setter<S, T, A, B> {
8    #[allow(clippy::type_complexity)]
9    modify: Box<dyn Fn(S, &dyn Fn(A) -> B) -> T>,
10}
11
12/// A simple (monomorphic) setter where `S == T` and `A == B`.
13pub type SimpleSetter<S, A> = Setter<S, S, A, A>;
14
15impl<S, T, A, B> Optic for Setter<S, T, A, B> {}
16
17impl<S, T, A, B> Setter<S, T, A, B> {
18    pub fn new(modify: impl Fn(S, &dyn Fn(A) -> B) -> T + 'static) -> Self {
19        Self {
20            modify: Box::new(modify),
21        }
22    }
23
24    /// Modify the focus using a function.
25    pub fn over(&self, s: S, f: impl Fn(A) -> B) -> T {
26        (self.modify)(s, &f)
27    }
28
29    /// Set the focus to a constant value.
30    pub fn set(&self, s: S, b: B) -> T
31    where
32        B: Clone,
33    {
34        (self.modify)(s, &|_| b.clone())
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[derive(Debug, Clone, PartialEq)]
43    struct Point {
44        x: i32,
45        y: i32,
46    }
47
48    fn point_x_setter() -> SimpleSetter<Point, i32> {
49        Setter::new(|p: Point, f: &dyn Fn(i32) -> i32| Point { x: f(p.x), ..p })
50    }
51
52    #[test]
53    fn setter_over() {
54        let setter = point_x_setter();
55        let p = Point { x: 1, y: 2 };
56        let result = setter.over(p, |x| x + 10);
57        assert_eq!(result, Point { x: 11, y: 2 });
58    }
59
60    #[test]
61    fn setter_set() {
62        let setter = point_x_setter();
63        let p = Point { x: 1, y: 2 };
64        let result = setter.set(p, 99);
65        assert_eq!(result, Point { x: 99, y: 2 });
66    }
67
68    #[test]
69    fn setter_identity_law() {
70        // over(s, id) == s
71        let setter = point_x_setter();
72        let p = Point { x: 5, y: 10 };
73        let result = setter.over(p.clone(), |x| x);
74        assert_eq!(result, p);
75    }
76
77    #[test]
78    fn setter_from_lens() {
79        use crate::lens::Lens;
80        let lens = Lens::new(|p: &Point| p.x, |p: Point, x| Point { x, ..p });
81        let setter = lens.to_setter();
82        let p = Point { x: 1, y: 2 };
83        let result = setter.over(p, |x| x + 10);
84        assert_eq!(result, Point { x: 11, y: 2 });
85    }
86
87    #[test]
88    fn setter_from_prism() {
89        use crate::prism::Prism;
90
91        #[derive(Debug, Clone, PartialEq)]
92        enum Val {
93            Int(i32),
94            Str(String),
95        }
96        let prism = Prism::new(
97            |v: Val| match v {
98                Val::Int(n) => Ok(n),
99                Val::Str(s) => Err(Val::Str(s)),
100            },
101            Val::Int,
102        );
103        let setter = prism.to_setter();
104        assert_eq!(setter.over(Val::Int(5), |x| x * 2), Val::Int(10));
105        assert_eq!(
106            setter.over(Val::Str("hi".into()), |x| x * 2),
107            Val::Str("hi".into())
108        );
109    }
110}