Skip to main content

karpal_optics/
setter.rs

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