๐Ÿฆ€ 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

209: Affine Traversal

Difficulty: โญโญโญ Level: Advanced A Lens that might find nothing โ€” `preview` returns `Option<A>`, and `over` is a no-op when the target is absent.

The Problem This Solves

You have a struct with optional fields, or a `HashMap` where a key might not exist, or the first element of a possibly-empty `Vec`. You want to read or modify those values when they exist โ€” and silently do nothing when they don't. With a plain `Option`, you handle it inline every time:
// Reading an optional email
if let Some(ref email) = user.email {
 println!("Email: {}", email);
}

// Updating an optional email to uppercase
if let Some(ref email) = user.email {
 user = User { email: Some(email.to_uppercase()), ..user };
}

// Now do the same for phone, address, profile_pic, ...
// Each field needs its own if-let block, every time
With a `HashMap` lookup:
// Get, transform, re-insert
if let Some(v) = map.get("key") {
 let new_v = transform(v.clone());
 map.insert("key".to_string(), new_v);
}
These are all the same pattern: "if it exists, do something; if not, leave the structure unchanged." An Affine Traversal bundles that pattern into a reusable optic โ€” one value that knows both how to find the target (returning `Option`) and how to set it. This exists to solve exactly that pain.

The Intuition

In the optics hierarchy: An Affine Traversal has two operations: 1. `preview` โ€” try to get the value: returns `Option<A>`. Returns `None` if the target doesn't exist. 2. `set` / `over` โ€” modify the value if it exists; return the structure unchanged if it doesn't. Analogy: An Affine Traversal is like a GPS navigator that might say "there's no route." `preview` is "can you find me a route?" โ€” returns `Some(route)` or `None`. `over` is "take this route and add 10 minutes" โ€” if there's no route, you're still at the same location.
// Affine Traversal: S is the whole structure, A is the optional focus
struct Affine<S, A> {
 preview: Box<dyn Fn(&S) -> Option<A>>,  // read: might find nothing
 set:     Box<dyn Fn(A, &S) -> S>,       // write: always returns S
                                          //        (unchanged if not present)
}

// over: derived from preview + set
// If target exists โ†’ apply f, write back
// If target absent โ†’ return structure unchanged
fn over(&self, f: impl FnOnce(A) -> A, s: &S) -> S where S: Clone {
 match (self.preview)(s) {
     Some(v) => (self.set)(f(v), s),
     None    => s.clone(),              // no-op
 }
}
The name "affine" comes from mathematics โ€” an affine traversal focuses on at most one element (0 or 1), bridging the gap between a Lens (exactly 1) and a full Traversal (0 to many).

How It Works in Rust

// Step 1: The struct โ€” two boxed closures
struct Affine<S, A> {
 preview: Box<dyn Fn(&S) -> Option<A>>,
 set:     Box<dyn Fn(A, &S) -> S>,
}

// Step 2: Affine for an optional struct field
#[derive(Clone)]
struct User {
 name:  String,
 email: Option<String>,
 phone: Option<String>,
}

fn email_affine() -> Affine<User, String> {
 Affine::new(
     |u| u.email.clone(),               // preview: return the Option<String>
     |e, u| User { email: Some(e), ..u.clone() },  // set: replace email
 )
}

let alice = User { name: "Alice".into(), email: Some("alice@x.com".into()), phone: None };
let bob   = User { name: "Bob".into(),   email: None,                       phone: Some("555".into()) };

// preview โ€” gets Option<A>
email_affine().preview(&alice);  // Some("alice@x.com")
email_affine().preview(&bob);    // None

// over โ€” modifies when present, no-op when absent
let alice2 = email_affine().over(|e| e.to_uppercase(), &alice);
alice2.email;  // Some("ALICE@X.COM")

let bob2 = email_affine().over(|e| e.to_uppercase(), &bob);
bob2.email;    // None  โ† no-op, bob had no email

// Step 3: Affine for HashMap โ€” "lens into a key that might not exist"
fn at_key(key: String) -> Affine<HashMap<String, String>, String> {
 let k1 = key.clone();
 let k2 = key;
 Affine::new(
     move |m| m.get(&k1).cloned(),               // preview: might be None
     move |v, m| {
         let mut m2 = m.clone();
         m2.insert(k2.clone(), v);               // set: always inserts
         m2
     },
 )
}

let m: HashMap<String, String> = [("a".into(), "1".into())].into();
at_key("a".into()).preview(&m);   // Some("1")
at_key("z".into()).preview(&m);   // None

// Step 4: Composing two Affine Traversals
// "get the email of the user at key 'admin' in a HashMap<String, User>"
// compose_affine(map_at_admin, user_email):
//   preview = map_at_admin.preview(m).and_then(|u| user_email.preview(&u))
//   over    = if key exists, modify user's email; otherwise no-op

fn compose_affine<S, A, B>(
 outer: Affine<S, A>,  // S -> Option<A>
 inner: Affine<A, B>,  // A -> Option<B>
) -> Affine<S, B>
where S: Clone + 'static, A: Clone + 'static, B: 'static
{
 // preview: chain the two Options with and_then
 // set: find the outer target, modify it with inner's set, write back
 // ...
}

What This Unlocks

Key Differences

ConceptOCamlRust
Affine typeRecord `{ preview: 's -> 'a option; set: 'a -> 's -> 's }``struct Affine<S, A>` with two `Box<dyn Fn>` fields
Optional fields`string option` fields in record`Option<String>` fields in struct
Map type`StringMap` via functor / association list`HashMap<String, String>` from `std::collections`
`over` on absentPattern match โ†’ return `s` unchanged`match preview(s) { None => s.clone() }` โ€” requires `Clone` bound
CompositionOption chaining via `>>=``and_then` for preview; match-then-set for the write direction
// Example 209: Affine Traversal โ€” At Most One Focus

use std::collections::HashMap;

struct Affine<S, A> {
    preview: Box<dyn Fn(&S) -> Option<A>>,
    set: Box<dyn Fn(A, &S) -> S>,
}

impl<S: 'static, A: 'static> Affine<S, A> {
    fn new(
        preview: impl Fn(&S) -> Option<A> + 'static,
        set: impl Fn(A, &S) -> S + 'static,
    ) -> Self {
        Affine { preview: Box::new(preview), set: Box::new(set) }
    }

    fn over(&self, f: impl FnOnce(A) -> A, s: &S) -> S where S: Clone {
        match (self.preview)(s) {
            Some(v) => (self.set)(f(v), s),
            None => s.clone(),
        }
    }
}

// Approach 1: Affine for optional fields
#[derive(Debug, Clone, PartialEq)]
struct User {
    name: String,
    email: Option<String>,
    phone: Option<String>,
}

fn email_affine() -> Affine<User, String> {
    Affine::new(
        |u| u.email.clone(),
        |e, u| User { email: Some(e), ..u.clone() },
    )
}

fn phone_affine() -> Affine<User, String> {
    Affine::new(
        |u| u.phone.clone(),
        |p, u| User { phone: Some(p), ..u.clone() },
    )
}

// Approach 2: Affine for HashMap lookups
fn at_key(key: String) -> Affine<HashMap<String, String>, String> {
    let k1 = key.clone();
    let k2 = key;
    Affine::new(
        move |m| m.get(&k1).cloned(),
        move |v, m| { let mut m2 = m.clone(); m2.insert(k2.clone(), v); m2 },
    )
}

// Approach 3: Composition
fn compose_affine<S: Clone + 'static, A: Clone + 'static, B: 'static>(
    outer: Affine<S, A>,
    inner: Affine<A, B>,
) -> Affine<S, B> {
    let op = outer.preview;
    let os = outer.set;
    let ip = inner.preview;
    let is = inner.set;
    Affine::new(
        move |s| (op)(s).and_then(|a| (ip)(&a)),
        move |b, s| match (op)(s) {
            Some(a) => (os)((is)(b, &a), s),
            None => s.clone(),
        },
    )
}

fn main() {
    let user1 = User { name: "Alice".into(), email: Some("alice@x.com".into()), phone: None };
    let user2 = User { name: "Bob".into(), email: None, phone: Some("555-1234".into()) };

    let ea = email_affine();
    assert_eq!((ea.preview)(&user1), Some("alice@x.com".into()));
    assert_eq!((ea.preview)(&user2), None);

    // Set
    let u = (ea.set)("new@x.com".into(), &user1);
    assert_eq!(u.email, Some("new@x.com".into()));

    // Over on present
    let u2 = email_affine().over(|e| e.to_uppercase(), &user1);
    assert_eq!(u2.email, Some("ALICE@X.COM".into()));

    // Over on absent = no-op
    let u3 = email_affine().over(|e| e.to_uppercase(), &user2);
    assert_eq!(u3.email, None);

    // HashMap lookup
    let m: HashMap<String, String> = [("a".into(), "1".into()), ("b".into(), "2".into())].into();
    let at_a = at_key("a".into());
    assert_eq!((at_a.preview)(&m), Some("1".into()));
    assert_eq!((at_key("z".into()).preview)(&m), None);

    println!("โœ“ All tests passed");
}

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

    #[test]
    fn test_affine_preview() {
        let u = User { name: "X".into(), email: Some("x@y".into()), phone: None };
        assert_eq!((email_affine().preview)(&u), Some("x@y".into()));
        assert_eq!((phone_affine().preview)(&u), None);
    }

    #[test]
    fn test_affine_over_absent() {
        let u = User { name: "X".into(), email: None, phone: None };
        let u2 = email_affine().over(|_| "forced".into(), &u);
        assert_eq!(u2.email, None); // no-op
    }

    #[test]
    fn test_hashmap_affine() {
        let m: HashMap<String, String> = [("k".into(), "v".into())].into();
        let a = at_key("k".into());
        assert_eq!((a.preview)(&m), Some("v".into()));
        let m2 = (a.set)("new".into(), &m);
        assert_eq!(m2.get("k"), Some(&"new".into()));
    }
}
(* Example 209: Affine Traversal โ€” At Most One Focus *)

(* An affine traversal focuses on at most one value.
   It's the combination of a prism (might not exist) and a lens (if it exists, it's unique).
   Think: "optional field accessor" *)

type ('s, 'a) affine = {
  preview : 's -> 'a option;
  set     : 'a -> 's -> 's;
}

(* Approach 1: Affine for optional record fields *)
type user = {
  name  : string;
  email : string option;
  phone : string option;
}

let email_affine : (user, string) affine = {
  preview = (fun u -> u.email);
  set = (fun e u -> { u with email = Some e });
}

let phone_affine : (user, string) affine = {
  preview = (fun u -> u.phone);
  set = (fun p u -> { u with phone = Some p });
}

(* Approach 2: Affine for map lookups *)
module StringMap = Map.Make(String)

let at_key (key : string) : (string StringMap.t, string) affine = {
  preview = (fun m -> StringMap.find_opt key m);
  set = (fun v m -> StringMap.add key v m);
}

(* Approach 3: Affine combinators *)
let over_affine (a : ('s, 'a) affine) (f : 'a -> 'a) (s : 's) : 's =
  match a.preview s with
  | Some v -> a.set (f v) s
  | None -> s

let compose_affine (outer : ('s, 'a) affine) (inner : ('a, 'b) affine) : ('s, 'b) affine = {
  preview = (fun s ->
    match outer.preview s with
    | Some a -> inner.preview a
    | None -> None);
  set = (fun b s ->
    match outer.preview s with
    | Some a -> outer.set (inner.set b a) s
    | None -> s);
}

(* === Tests === *)
let () =
  let user1 = { name = "Alice"; email = Some "alice@x.com"; phone = None } in
  let user2 = { name = "Bob"; email = None; phone = Some "555-1234" } in

  (* Preview *)
  assert (email_affine.preview user1 = Some "alice@x.com");
  assert (email_affine.preview user2 = None);
  assert (phone_affine.preview user2 = Some "555-1234");

  (* Set *)
  let u = email_affine.set "new@x.com" user1 in
  assert (u.email = Some "new@x.com");

  (* Over *)
  let u2 = over_affine email_affine String.uppercase_ascii user1 in
  assert (u2.email = Some "ALICE@X.COM");

  (* Over on missing field is no-op *)
  let u3 = over_affine email_affine String.uppercase_ascii user2 in
  assert (u3.email = None);

  (* Map lookup *)
  let m = StringMap.of_seq (List.to_seq [("a", "1"); ("b", "2")]) in
  let at_a = at_key "a" in
  assert (at_a.preview m = Some "1");
  assert ((at_key "z").preview m = None);

  let m2 = at_a.set "99" m in
  assert (at_a.preview m2 = Some "99");

  print_endline "โœ“ All tests passed"

๐Ÿ“Š Detailed Comparison

Comparison: Example 209 โ€” Affine Traversal

Affine Type

OCaml

๐Ÿช Show OCaml equivalent
type ('s, 'a) affine = {
preview : 's -> 'a option;
set     : 'a -> 's -> 's;
}

Rust

struct Affine<S, A> {
 preview: Box<dyn Fn(&S) -> Option<A>>,
 set: Box<dyn Fn(A, &S) -> S>,
}

Map Lookup

OCaml

๐Ÿช Show OCaml equivalent
let at_key key = {
preview = (fun m -> StringMap.find_opt key m);
set = (fun v m -> StringMap.add key v m);
}

Rust

fn at_key(key: String) -> Affine<HashMap<String, String>, String> {
 let k1 = key.clone();
 let k2 = key;
 Affine::new(
     move |m| m.get(&k1).cloned(),
     move |v, m| { let mut m2 = m.clone(); m2.insert(k2.clone(), v); m2 },
 )
}

Over (No-op When Absent)

OCaml

๐Ÿช Show OCaml equivalent
let over_affine a f s =
match a.preview s with
| Some v -> a.set (f v) s
| None -> s

Rust

fn over(&self, f: impl FnOnce(A) -> A, s: &S) -> S where S: Clone {
 match (self.preview)(s) {
     Some(v) => (self.set)(f(v), s),
     None => s.clone(),
 }
}