🦀 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

205: Lens Modify — Transform a Field With a Function

Difficulty: ⭐⭐⭐ Level: Intermediate `modify` applies a transformation function through a Lens — the most practical Lens operation in real code.

The Problem This Solves

`set` replaces a field with a literal value. That covers some cases, but real code usually transforms: "increment the counter", "double the price", "append to the list", "uppercase the label". For each transformation you'd write a separate function — `increment_count`, `double_price`, `append_tag` — each one repeating the same struct-update boilerplate. You need a way to say: "apply this function to this field and give me back a new struct." That's `modify`. Without it, you'd write `set(lens, f(view(lens, s)), s)` everywhere — mixing the "how to navigate" concern with the "what to do" concern at every call site. `modify` separates these concerns cleanly: the Lens knows the path, the function knows the transformation. You can pass any function; the Lens handles the structural surgery. Pipelines become a sequence of `modify` calls, each readable and independent. This example exists to solve exactly that pain.

The Intuition

`modify` is defined in terms of `get` and `set`:
modify lens f s  =  set lens (f (get lens s)) s
In English: "look at the value, run it through `f`, put the result back." The struct's other fields are untouched. But it's easier to think of `modify` as the primary operation and `get`/`set` as special cases: In practice, you call `modify` or `over` (same thing, different name in different libraries) far more often than raw `get`/`set`.

How It Works in Rust

Adding `modify` to the Lens:
impl<S: 'static, A: 'static> Lens<S, A> {
 fn modify(&self, f: impl FnOnce(A) -> A, s: &S) -> S {
     // get the current value, transform it, set it back
     (self.set)(f((self.get)(s)), s)
 }
}
Basic usage:
let c = Counter { count: 5, label: "clicks".into() };
let l = count_lens();

l.modify(|n| n + 1,  &c).count  // 6
l.modify(|n| n * 2,  &c).count  // 10
l.modify(|_| 0,      &c).count  // 0  (constant function = set)
Modify through a composed Lens:
let o = Outer { inner: Inner { value: 10 }, tag: "test".into() };
let deep = inner_lens().compose(value_lens());

// Transform the deeply nested value — one line
let o2 = deep.modify(|v| v + 1, &o);
assert_eq!(o2.inner.value, 11);
assert_eq!(o2.tag, "test");  // other fields untouched
Pipeline of modifications: When you need several sequential transformations, chain `modify` calls:
fn pipeline(c: &Counter) -> Counter {
 let c2 = count_lens().modify(|n| n + 10, c);   // step 1: add 10
 count_lens().modify(|n| n * 2, &c2)              // step 2: double
}

let result = pipeline(&Counter { count: 5, label: "x".into() });
// (5 + 10) * 2 = 30
Each step is its own line. Each step is independently readable. There's no nested expression syntax to parse.

What This Unlocks

Key Differences

ConceptOCamlRust
Function name`modify` or `over`, infix `%~` operator`modify` or `over` method on `Lens<S,A>`
Infix pipeline`s \> (count_l %~ ((+) 1)) \> (count_l %~ ( *2))`Sequential `let` bindings — no infix
Partial application`modify count_l ((+) 1)` returns `'s -> 's`Closures required: `\c\l.modify(\n\n+1, c)`
CurryingNatural — `modify lens` is a functionNot idiomatic — wrap in a closure
Pipelines`\>` operator chains transformations cleanly`let c2 = …; let c3 = …;` sequential bindings
// Example 205: Lens Modify — Apply Function Through a Lens

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) }
    }

    // The key operation: modify the focused value with a function
    fn modify(&self, f: impl FnOnce(A) -> A, s: &S) -> S {
        (self.set)(f((self.get)(s)), s)
    }

    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) }),
        }
    }
}

// Approach 1: Basic modify on flat structs
#[derive(Debug, Clone, PartialEq)]
struct Counter { count: i32, label: String }

fn count_lens() -> Lens<Counter, i32> {
    Lens::new(|c| c.count, |n, c| Counter { count: n, ..c.clone() })
}

// Approach 2: Modify through composed lenses
#[derive(Debug, Clone, PartialEq)]
struct Inner { value: i32 }

#[derive(Debug, Clone, PartialEq)]
struct Outer { inner: Inner, tag: String }

fn inner_lens() -> Lens<Outer, Inner> {
    Lens::new(|o| o.inner.clone(), |i, o| Outer { inner: i, ..o.clone() })
}

fn value_lens() -> Lens<Inner, i32> {
    Lens::new(|i| i.value, |v, i| Inner { value: v })
}

// Approach 3: Pipeline of modifications
fn pipeline(c: &Counter) -> Counter {
    let l = count_lens();
    let c2 = l.modify(|n| n + 10, c);
    let l = count_lens();
    l.modify(|n| n * 2, &c2)
}

fn main() {
    let c = Counter { count: 5, label: "clicks".into() };
    let l = count_lens();

    // Basic modify
    assert_eq!(l.modify(|n| n + 1, &c).count, 6);
    assert_eq!(l.modify(|n| n * 2, &c).count, 10);
    assert_eq!(l.modify(|_| 0, &c).count, 0);

    // Modify through composition
    let o = Outer { inner: Inner { value: 10 }, tag: "test".into() };
    let deep = inner_lens().compose(value_lens());
    let o2 = deep.modify(|v| v + 1, &o);
    assert_eq!(o2.inner.value, 11);
    assert_eq!(o2.tag, "test");

    // Pipeline
    let c2 = pipeline(&c);
    assert_eq!(c2.count, 30); // (5 + 10) * 2
    assert_eq!(c2.label, "clicks");

    println!("✓ All tests passed");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_modify_increment() {
        let c = Counter { count: 0, label: "x".into() };
        assert_eq!(count_lens().modify(|n| n + 1, &c).count, 1);
    }

    #[test]
    fn test_modify_preserves_fields() {
        let c = Counter { count: 5, label: "important".into() };
        let c2 = count_lens().modify(|n| n * 10, &c);
        assert_eq!(c2.label, "important");
    }

    #[test]
    fn test_deep_modify() {
        let o = Outer { inner: Inner { value: 100 }, tag: "t".into() };
        let l = inner_lens().compose(value_lens());
        assert_eq!(l.modify(|v| v - 50, &o).inner.value, 50);
    }

    #[test]
    fn test_pipeline() {
        let c = Counter { count: 10, label: "y".into() };
        assert_eq!(pipeline(&c).count, 40); // (10+10)*2
    }
}
(* Example 205: Lens Modify — Apply Function Through a Lens *)

type ('s, 'a) lens = {
  get : 's -> 'a;
  set : 'a -> 's -> 's;
}

(* modify: the key operation — apply a function to the focused value *)
let modify (l : ('s, 'a) lens) (f : 'a -> 'a) (s : 's) : 's =
  l.set (f (l.get s)) s

(* Approach 1: Basic modify on flat records *)
type counter = { count : int; label : string }

let count_lens = {
  get = (fun c -> c.count);
  set = (fun n c -> { c with count = n });
}

let increment = modify count_lens (( + ) 1)
let double = modify count_lens (( * ) 2)
let reset = modify count_lens (fun _ -> 0)

(* Approach 2: Modify through composed lenses *)
let compose outer inner = {
  get = (fun s -> inner.get (outer.get s));
  set = (fun b s -> outer.set (inner.set b (outer.get s)) s);
}

type inner = { value : int }
type outer = { inner : inner; tag : string }

let inner_lens = {
  get = (fun o -> o.inner);
  set = (fun i o -> { o with inner = i });
}

let value_lens = {
  get = (fun i -> i.value);
  set = (fun v i -> { i with value = v });
}

let outer_value = compose inner_lens value_lens

let increment_value = modify outer_value (( + ) 1)

(* Approach 3: Multiple modifications chained *)
let ( %~ ) lens f = modify lens f

let pipeline s =
  s
  |> count_lens %~ (( + ) 10)
  |> count_lens %~ (( * ) 2)

(* === Tests === *)
let () =
  let c = { count = 5; label = "clicks" } in

  (* Basic modify *)
  assert (increment c = { count = 6; label = "clicks" });
  assert (double c = { count = 10; label = "clicks" });
  assert (reset c = { count = 0; label = "clicks" });

  (* Modify through composition *)
  let o = { inner = { value = 10 }; tag = "test" } in
  let o2 = increment_value o in
  assert (o2.inner.value = 11);
  assert (o2.tag = "test");

  (* Chained modifications *)
  let c2 = pipeline c in
  assert (c2.count = 30); (* (5 + 10) * 2 *)

  (* Modify preserves other fields *)
  assert (c2.label = "clicks");

  print_endline "✓ All tests passed"

📊 Detailed Comparison

Comparison: Example 205 — Lens Modify

The modify Operation

OCaml

🐪 Show OCaml equivalent
let modify l f s = l.set (f (l.get s)) s

let increment = modify count_lens (( + ) 1)
let double = modify count_lens (( * ) 2)

Rust

fn modify(&self, f: impl FnOnce(A) -> A, s: &S) -> S {
 (self.set)(f((self.get)(s)), s)
}

let incremented = count_lens().modify(|n| n + 1, &counter);
let doubled = count_lens().modify(|n| n * 2, &counter);

Pipeline Style

OCaml

🐪 Show OCaml equivalent
let ( %~ ) lens f = modify lens f

let result = counter
|> count_lens %~ (( + ) 10)
|> count_lens %~ (( * ) 2)

Rust

fn pipeline(c: &Counter) -> Counter {
 let l = count_lens();
 let c2 = l.modify(|n| n + 10, c);
 count_lens().modify(|n| n * 2, &c2)
}