// Example 204: Lens Composition — Zoom Into Nested Structs
struct Lens<S, A> {
get: Box<dyn Fn(&S) -> A>,
set: Box<dyn Fn(A, &S) -> S>,
}
impl<S: 'static, A: 'static> Lens<S, A> {
fn new(get: impl Fn(&S) -> A + 'static, set: impl Fn(A, &S) -> S + 'static) -> Self {
Lens { get: Box::new(get), set: Box::new(set) }
}
// Approach 1: Composition method
fn compose<B: 'static>(self, inner: Lens<A, B>) -> Lens<S, B>
where A: Clone {
let og = self.get;
let os = self.set;
let ig = inner.get;
let is = inner.set;
Lens {
get: Box::new(move |s| (ig)(&(og)(s))),
set: Box::new(move |b, s| {
let a = (og)(s);
let a2 = (is)(b, &a);
(os)(a2, s)
}),
}
}
fn over(&self, f: impl FnOnce(A) -> A, s: &S) -> S {
(self.set)(f((self.get)(s)), s)
}
}
// Approach 2: Three-level deep nesting
#[derive(Debug, Clone, PartialEq)]
struct Street { number: u32, name: String }
#[derive(Debug, Clone, PartialEq)]
struct Address { street: Street, city: String }
#[derive(Debug, Clone, PartialEq)]
struct Person { pname: String, address: Address }
fn address_l() -> Lens<Person, Address> {
Lens::new(|p| p.address.clone(), |a, p| Person { address: a, ..p.clone() })
}
fn street_l() -> Lens<Address, Street> {
Lens::new(|a| a.street.clone(), |s, a| Address { street: s, ..a.clone() })
}
fn number_l() -> Lens<Street, u32> {
Lens::new(|s| s.number, |n, s| Street { number: n, ..s.clone() })
}
fn name_l() -> Lens<Street, String> {
Lens::new(|s| s.name.clone(), |n, s| Street { name: n, ..s.clone() })
}
// Approach 3: Macro for chained composition
macro_rules! compose_lenses {
($l:expr) => { $l };
($l:expr, $($rest:expr),+) => {
$l.compose(compose_lenses!($($rest),+))
};
}
fn main() {
let alice = Person {
pname: "Alice".into(),
address: Address {
street: Street { number: 42, name: "Elm Street".into() },
city: "Springfield".into(),
},
};
// Compose lenses
let person_number = address_l().compose(street_l()).compose(number_l());
assert_eq!((person_number.get)(&alice), 42);
let alice2 = (person_number.set)(99, &alice);
assert_eq!(alice2.address.street.number, 99);
assert_eq!(alice2.address.city, "Springfield");
// Modify through lens
let person_number = address_l().compose(street_l()).compose(number_l());
let alice3 = person_number.over(|n| n + 1, &alice);
assert_eq!((address_l().compose(street_l()).compose(number_l()).get)(&alice3), 43);
// Macro syntax
let street_name = compose_lenses!(address_l(), street_l(), name_l());
assert_eq!((street_name.get)(&alice), "Elm Street");
println!("✓ All tests passed");
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Person {
Person {
pname: "Bob".into(),
address: Address {
street: Street { number: 10, name: "Main St".into() },
city: "NYC".into(),
},
}
}
#[test]
fn test_compose_get() {
let l = address_l().compose(street_l()).compose(number_l());
assert_eq!((l.get)(&sample()), 10);
}
#[test]
fn test_compose_set() {
let l = address_l().compose(street_l()).compose(number_l());
let p = (l.set)(20, &sample());
assert_eq!(p.address.street.number, 20);
assert_eq!(p.pname, "Bob");
}
#[test]
fn test_compose_over() {
let l = address_l().compose(street_l()).compose(number_l());
let p = l.over(|n| n * 3, &sample());
assert_eq!((address_l().compose(street_l()).compose(number_l()).get)(&p), 30);
}
}
(* Example 204: Lens Composition — Zoom Into Nested Structs *)
type ('s, 'a) lens = {
get : 's -> 'a;
set : 'a -> 's -> 's;
}
(* Approach 1: Direct composition *)
let compose (outer : ('s, 'a) lens) (inner : ('a, 'b) lens) : ('s, 'b) lens = {
get = (fun s -> inner.get (outer.get s));
set = (fun b s ->
let a = outer.get s in
let a' = inner.set b a in
outer.set a' s);
}
(* Infix operator for readability *)
let ( |>> ) = compose
(* Approach 2: Three-level deep nesting *)
type street = { number : int; name : string }
type address = { street : street; city : string }
type person = { pname : string; address : address }
let address_l : (person, address) lens = {
get = (fun p -> p.address);
set = (fun a p -> { p with address = a });
}
let street_l : (address, street) lens = {
get = (fun a -> a.street);
set = (fun s a -> { a with street = s });
}
let number_l : (street, int) lens = {
get = (fun s -> s.number);
set = (fun n s -> { s with number = n });
}
let name_l : (street, string) lens = {
get = (fun s -> s.name);
set = (fun n s -> { s with name = n });
}
(* Compose to reach deeply nested fields *)
let person_street_number = address_l |>> street_l |>> number_l
let person_street_name = address_l |>> street_l |>> name_l
(* Approach 3: modify via lens *)
let over lens f s = lens.set (f (lens.get s)) s
(* === Tests === *)
let () =
let alice = {
pname = "Alice";
address = {
street = { number = 42; name = "Elm Street" };
city = "Springfield";
};
} in
(* Get through composed lens *)
assert (person_street_number.get alice = 42);
assert (person_street_name.get alice = "Elm Street");
(* Set through composed lens *)
let alice2 = person_street_number.set 99 alice in
assert (alice2.address.street.number = 99);
assert (alice2.address.city = "Springfield"); (* unchanged *)
assert (alice2.pname = "Alice"); (* unchanged *)
(* Modify through composed lens *)
let alice3 = over person_street_number (fun n -> n + 1) alice in
assert (person_street_number.get alice3 = 43);
(* Composition is associative *)
let l1 = (address_l |>> street_l) |>> number_l in
let l2 = address_l |>> (street_l |>> number_l) in
assert (l1.get alice = l2.get alice);
assert (l1.set 10 alice = l2.set 10 alice);
print_endline "✓ All tests passed"