Skip to main content

karpal_optics/
getter.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::optic::Optic;
5
6/// A read-only optic that extracts a single focus from a source.
7///
8/// This is the "getter" component of a lens, without any modification capability.
9pub struct Getter<S, A> {
10    get: fn(&S) -> A,
11}
12
13impl<S, A> Optic for Getter<S, A> {}
14
15impl<S, A> Getter<S, A> {
16    pub fn new(get: fn(&S) -> A) -> Self {
17        Self { get }
18    }
19
20    pub fn get(&self, s: &S) -> A {
21        (self.get)(s)
22    }
23
24    /// Compose with another getter for deeper access.
25    pub fn then<B>(self, inner: Getter<A, B>) -> ComposedGetter<S, B>
26    where
27        S: 'static,
28        A: 'static,
29        B: 'static,
30    {
31        let outer_get = self.get;
32        let inner_get = inner.get;
33        ComposedGetter {
34            get: Box::new(move |s| inner_get(&outer_get(s))),
35        }
36    }
37}
38
39/// A composed getter using boxed closures (from composition or conversion).
40pub struct ComposedGetter<S, A> {
41    get: Box<dyn Fn(&S) -> A>,
42}
43
44impl<S, A> Optic for ComposedGetter<S, A> {}
45
46impl<S, A> ComposedGetter<S, A> {
47    pub fn get(&self, s: &S) -> A {
48        (self.get)(s)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[derive(Clone)]
57    struct Point {
58        x: f64,
59        y: f64,
60    }
61
62    #[test]
63    fn getter_get() {
64        let getter = Getter::new(|p: &Point| p.x);
65        let p = Point { x: 1.0, y: 2.0 };
66        assert!((getter.get(&p) - 1.0).abs() < f64::EPSILON);
67    }
68
69    #[test]
70    fn getter_composition() {
71        #[derive(Clone)]
72        struct Line {
73            start: Point,
74        }
75        let line_start = Getter::new(|l: &Line| l.start.clone());
76        let point_x = Getter::new(|p: &Point| p.x);
77        let composed = line_start.then(point_x);
78        let line = Line {
79            start: Point { x: 3.0, y: 4.0 },
80        };
81        assert!((composed.get(&line) - 3.0).abs() < f64::EPSILON);
82    }
83
84    #[test]
85    fn getter_from_lens() {
86        use crate::lens::Lens;
87        let lens = Lens::new(|p: &Point| p.x, |p: Point, x| Point { x, ..p });
88        let getter = lens.to_getter();
89        let p = Point { x: 5.0, y: 6.0 };
90        assert!((getter.get(&p) - 5.0).abs() < f64::EPSILON);
91    }
92
93    #[test]
94    fn getter_from_iso() {
95        use crate::iso::Iso;
96        let iso = Iso::new(|n: &i32| *n as f64, |f: f64| f as i32);
97        let getter = iso.to_getter();
98        assert!((getter.get(&42) - 42.0).abs() < f64::EPSILON);
99    }
100}