🦀 Functional Rust
🎬 Traits & Generics Shared behaviour, static vs dynamic dispatch, zero-cost polymorphism.
📝 Text version (for readers / accessibility)

• Traits define shared behaviour — like interfaces but with default implementations

• Generics with trait bounds: fn process(item: T) — monomorphized at compile time

• Static dispatch (impl Trait) = zero cost; dynamic dispatch (dyn Trait) = runtime flexibility via vtable

• Blanket implementations apply traits to all types matching a bound

• Associated types and supertraits enable complex type relationships

204: Lens Composition — Zoom Into Nested Structs

Difficulty: ⭐⭐⭐ Level: Intermediate Compose two Lenses into one: reach any depth in a nested struct with a single accessor.

The Problem This Solves

You have a Lens from `Person → Address` and another from `Address → Street`. Now you want to read or update the street number on a `Person`. Without composition, you'd do it manually:
let addr = person_to_address.get(&person);
let street = address_to_street.get(&addr);
let number = street_to_number.get(&street);
// And for set:
let new_street = Street { number: 99, ..street };
let new_addr = Address { street: new_street, ..addr };
let new_person = Person { address: new_addr, ..person.clone() };
That's seven lines to change one field through two levels of nesting. You're hand-assembling what should be automatic. The power of composition is that lenses snap together: `A → B` composed with `B → C` gives you `A → C`. The get functions chain left-to-right; the set functions chain right-to-left. Once composed, the resulting Lens is indistinguishable from a Lens that was written directly. And composition is associative: `(A composed B) composed C` equals `A composed (B composed C)` — you can group the steps any way you want and get the same result. This example exists to solve exactly that pain.

The Intuition

Imagine you have two transparent pipes. The first goes from your hand to a box, the second goes from inside that box to a smaller box inside it. Composition is just joining the two pipes end-to-end: now you have one pipe from your hand straight to the innermost box. You don't have to think about the intermediate box at all. In code terms: `|number, person| address_set(street_set(number, address_get(person)), person)` The outer Lens doesn't need to know what `B` is internally. The inner Lens doesn't need to know anything about `A`. They just need to agree on the type `B` at their junction.

How It Works in Rust

Three one-level Lenses:
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() })
}
Compose them — the `compose` method:
impl<S: 'static, A: 'static> Lens<S, A> {
 fn compose<B: 'static>(self, inner: Lens<A, B>) -> Lens<S, B>
 where A: Clone {
     let og = self.get;   // outer get:  S -> A
     let os = self.set;   // outer set:  (A, S) -> S
     let ig = inner.get;  // inner get:  A -> B
     let is = inner.set;  // inner set:  (B, A) -> A
     Lens {
         get: Box::new(move |s| (ig)(&(og)(s))),    // S -> A -> B
         set: Box::new(move |b, s| {
             let a = (og)(s);                        // extract A from S
             let a2 = (is)(b, &a);                  // set B inside A -> new A
             (os)(a2, s)                             // set new A inside S
         }),
     }
 }
}
Use the composed Lens:
// Compose once
let person_number = address_l()
 .compose(street_l())
 .compose(number_l());   // Lens<Person, u32>

// Read — all the way from Person to u32
let n = (person_number.get)(&alice);    // 42

// Write — one line, three levels deep
let alice2 = (person_number.set)(99, &alice);
assert_eq!(alice2.address.street.number, 99);
assert_eq!(alice2.address.city, "Springfield");  // unchanged

// Modify (transform in place)
let alice3 = person_number.over(|n| n + 1, &alice);
Macro syntax for longer chains:
macro_rules! compose_lenses {
 ($l:expr) => { $l };
 ($l:expr, $($rest:expr),+) => { $l.compose(compose_lenses!($($rest),+)) };
}

let street_name = compose_lenses!(address_l(), street_l(), name_l());

What This Unlocks

Key Differences

ConceptOCamlRust
Composition syntaxInfix `\>>` operator: `a_l \>> b_l \>> c_l`Method chain: `a_l().compose(b_l()).compose(c_l())`
Intermediate allocationClosures, GC managed`Box<dyn Fn>` — each composition allocates two closures
Macro sugar`[%lens address.street.number]` in ppxCustom `compose_lenses!` macro
AssociativityHolds by constructionHolds by construction — same closure semantics
Reuse of composed lensClosures capture by value`.compose()` consumes `self` — store the result, or rebuild
// 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"

📊 Detailed Comparison

Comparison: Example 204 — Lens Composition

Composition Function

OCaml

🐪 Show OCaml equivalent
let compose outer inner = {
get = (fun s -> inner.get (outer.get s));
set = (fun b s ->
 let a = outer.get s in
 outer.set (inner.set b a) s);
}

let ( |>> ) = compose

Rust

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);
         (os)((is)(b, &a), s)
     }),
 }
}

Usage

OCaml

🐪 Show OCaml equivalent
let deep = address_l |>> street_l |>> number_l
let n = deep.get alice           (* 42 *)
let alice2 = deep.set 99 alice

Rust

let deep = address_l().compose(street_l()).compose(number_l());
let n = (deep.get)(&alice);       // 42
let alice2 = (deep.set)(99, &alice);