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

212: Van Laarhoven Lenses

Difficulty: 3 Level: Advanced Represent lenses as polymorphic functions over functors โ€” compose them with plain function composition.

The Problem This Solves

Standard lenses store `get` and `set` as two separate functions. When you compose them, you manually wire each level. The more lenses you compose, the more bookkeeping you do. And each operation (get, set, modify) requires branching to the right function. The Van Laarhoven encoding collapses all lens operations into one function type: `(a -> f b) -> (s -> f t)`. You pick the functor `f` to select the operation: Identity functor gives you `modify`, Const functor gives you `get`. One lens type, every operation. The deeper payoff: composition is free. Two Van Laarhoven lenses compose with ordinary function composition โ€” no special `compose_lens` needed. This is why Haskell's `lens` library works the way it does, and why understanding it unlocks the entire optics ecosystem.

The Intuition

A Van Laarhoven lens is a function that says: "I know how to reach inside an `s` and do something with the `a` inside it, whatever 'something' means โ€” you tell me by giving me a function `a -> f b`." The magic is the functor `f`. If `f` is `Identity` (just wraps a value), the lens returns a modified structure. If `f` is `Const` (ignores the second argument, just holds a read value), the lens returns the extracted value. Same lens code, different behavior depending on what you plug in. Rust can't express this directly because it would require "for any functor f" โ€” a rank-2 type. So the practical encoding stores two operations: an `over` function and a `get` function. Composition still chains the `over` functions.

How It Works in Rust

// Identity functor: wraps a value, used for modify/over
struct Identity<A>(A);
impl<A> Functor for Identity<A> {
 type Inner = A;
 fn map<B>(self, f: impl FnOnce(A) -> B) -> Identity<B> { Identity(f(self.0)) }
}

// Const functor: ignores map, used for get (holds the read value)
struct Const<A, B>(A, PhantomData<B>);
impl<A: Clone, B> Functor for Const<A, B> {
 type Inner = B;
 fn map<C>(self, _f: impl FnOnce(B) -> C) -> Const<A, C> {
     Const(self.0, PhantomData) // unchanged โ€” _f is never called
 }
}

// Practical encoding: store get and over separately
struct VLLens<S, A> {
 over_fn: Box<dyn Fn(&dyn Fn(&A) -> A, &S) -> S>, // modify
 get_fn:  Box<dyn Fn(&S) -> A>,                    // read
}

impl<S: 'static, A: 'static> VLLens<S, A> {
 fn get(&self, s: &S) -> A { (self.get_fn)(s) }
 fn over(&self, f: impl Fn(&A) -> A, s: &S) -> S { (self.over_fn)(&f, s) }
 fn set(&self, a: A, s: &S) -> S where A: Clone {
     self.over(move |_| a.clone(), s)
 }
}

// Composition: chain over functions โ€” the key advantage
fn compose<S, A, B>(outer: VLLens<S, A>, inner: VLLens<A, B>) -> VLLens<S, B> {
 // ... wires outer.over with inner.over
}

What This Unlocks

Key Differences

ConceptOCamlRust
Rank-2 typesNot available โ€” use module functorsNot available โ€” use trait encoding
FunctorModule signature `FUNCTOR``Functor` trait with GATs
CompositionFunction composition `(.)`Closure chaining in `compose()`
Practical encodingRecord of `run_identity` + `run_const`Struct with two `Box<dyn Fn>`
Identity functorDefined as module`struct Identity<A>(A)` with `Functor` impl
// Example 212: Van Laarhoven Lenses
//
// The Van Laarhoven encoding: a lens is a function polymorphic over functors:
//   type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
//
// Rust can't express rank-2 types directly, so we use trait-based encoding.

// Approach 1: Functor trait + Identity/Const
trait Functor {
    type Inner;
    type Mapped<B>;
    fn map<B>(self, f: impl FnOnce(Self::Inner) -> B) -> Self::Mapped<B>;
}

// Identity functor: for modify/over
struct Identity<A>(A);
impl<A> Functor for Identity<A> {
    type Inner = A;
    type Mapped<B> = Identity<B>;
    fn map<B>(self, f: impl FnOnce(A) -> B) -> Identity<B> { Identity(f(self.0)) }
}

// Const functor: for get
struct Const<A, B>(A, std::marker::PhantomData<B>);
impl<A, B> Functor for Const<A, B> {
    type Inner = B;
    type Mapped<C> = Const<A, C>;
    fn map<C>(self, _f: impl FnOnce(B) -> C) -> Const<A, C> {
        Const(self.0, std::marker::PhantomData)
    }
}

// Approach 2: Practical VL lens as two operations
struct VLLens<S, A> {
    over_fn: Box<dyn Fn(&dyn Fn(&A) -> A, &S) -> S>,
    get_fn: Box<dyn Fn(&S) -> A>,
}

impl<S: 'static, A: 'static> VLLens<S, A> {
    fn new(
        over_fn: impl Fn(&dyn Fn(&A) -> A, &S) -> S + 'static,
        get_fn: impl Fn(&S) -> A + 'static,
    ) -> Self {
        VLLens { over_fn: Box::new(over_fn), get_fn: Box::new(get_fn) }
    }

    fn view(&self, s: &S) -> A { (self.get_fn)(s) }
    fn over(&self, f: &dyn Fn(&A) -> A, s: &S) -> S { (self.over_fn)(f, s) }
    fn set(&self, a: A, s: &S) -> S where A: Clone {
        (self.over_fn)(&move |_| a.clone(), s)
    }

    // Approach 3: Composition โ€” just function composition!
    fn compose<B: 'static>(self, inner: VLLens<A, B>) -> VLLens<S, B>
    where A: Clone {
        let og = self.over_fn;
        let oget = self.get_fn;
        let ig = inner.over_fn;
        let iget = inner.get_fn;
        VLLens {
            over_fn: Box::new(move |f, s| {
                (og)(&|a: &A| (ig)(f, a), s)
            }),
            get_fn: Box::new(move |s| (iget)(&(oget)(s))),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
struct Person { name: String, age: u32 }

fn vl_name() -> VLLens<Person, String> {
    VLLens::new(
        |f, p| Person { name: f(&p.name), ..p.clone() },
        |p| p.name.clone(),
    )
}

fn vl_age() -> VLLens<Person, u32> {
    VLLens::new(
        |f, p| Person { age: f(&p.age), ..p.clone() },
        |p| p.age,
    )
}

#[derive(Debug, Clone, PartialEq)]
struct Address { street: String, city: String }

#[derive(Debug, Clone, PartialEq)]
struct Employee { emp_name: String, addr: Address }

fn vl_addr() -> VLLens<Employee, Address> {
    VLLens::new(
        |f, e| Employee { addr: f(&e.addr), ..e.clone() },
        |e| e.addr.clone(),
    )
}

fn vl_city() -> VLLens<Address, String> {
    VLLens::new(
        |f, a| Address { city: f(&a.city), ..a.clone() },
        |a| a.city.clone(),
    )
}

fn main() {
    let alice = Person { name: "Alice".into(), age: 30 };

    // View
    assert_eq!(vl_name().view(&alice), "Alice");
    assert_eq!(vl_age().view(&alice), 30);

    // Set
    let alice2 = vl_name().set("Alicia".into(), &alice);
    assert_eq!(vl_name().view(&alice2), "Alicia");

    // Over
    let alice3 = vl_age().over(&|a| a + 1, &alice);
    assert_eq!(vl_age().view(&alice3), 31);

    // Composed lens
    let emp = Employee {
        emp_name: "Bob".into(),
        addr: Address { street: "123 Main".into(), city: "NYC".into() },
    };
    let emp_city = vl_addr().compose(vl_city());
    assert_eq!(emp_city.view(&emp), "NYC");
    let emp2 = emp_city.set("LA".into(), &emp);
    assert_eq!(vl_addr().compose(vl_city()).view(&emp2), "LA");

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

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

    #[test]
    fn test_vl_view() {
        let p = Person { name: "X".into(), age: 1 };
        assert_eq!(vl_name().view(&p), "X");
    }

    #[test]
    fn test_vl_over() {
        let p = Person { name: "X".into(), age: 10 };
        let p2 = vl_age().over(&|a| a * 2, &p);
        assert_eq!(p2.age, 20);
    }

    #[test]
    fn test_vl_compose() {
        let emp = Employee {
            emp_name: "Y".into(),
            addr: Address { street: "S".into(), city: "C".into() },
        };
        let l = vl_addr().compose(vl_city());
        assert_eq!(l.view(&emp), "C");
        let emp2 = l.set("D".into(), &emp);
        assert_eq!(vl_addr().compose(vl_city()).view(&emp2), "D");
    }

    #[test]
    fn test_identity_functor() {
        let id = Identity(42);
        let id2 = id.map(|x| x + 1);
        assert_eq!(id2.0, 43);
    }
}
(* Example 212: Van Laarhoven Lenses *)

(* The Van Laarhoven encoding: a lens is a polymorphic function
   that works for ANY functor f:
   
   type ('s,'t,'a,'b) lens = forall f. Functor f => (a -> f b) -> s -> f t
   
   This single type unifies get, set, and modify! *)

(* Approach 1: Module-based Van Laarhoven in OCaml *)

module type FUNCTOR = sig
  type 'a t
  val map : ('a -> 'b) -> 'a t -> 'b t
end

(* Identity functor โ€” used for "modify" *)
module Identity = struct
  type 'a t = 'a
  let map f x = f x
  let run x = x
end

(* Const functor โ€” used for "get" *)
module Const (M : sig type t end) = struct
  type 'a t = M.t
  let map _f x = x
  let run x = x
end

(* A Van Laarhoven lens for a specific functor *)
module type VL_LENS = sig
  type s
  type a
  val lens : (module FUNCTOR with type 'x t = 'x) -> (a -> a) -> s -> s
  (* We can't truly express rank-2 types in OCaml, so we use modules *)
end

(* Approach 2: Practical encoding using two functions *)
(* In practice, OCaml can't do rank-2 polymorphism directly,
   so we encode VL lenses as a record with "for each functor" *)

type ('s, 'a) vl_lens = {
  run_identity : ('a -> 'a) -> 's -> 's;         (* over/modify *)
  run_const    : 's -> 'a;                         (* get *)
}

let vl_view l s = l.run_const s
let vl_over l f s = l.run_identity f s
let vl_set l a s = vl_over l (fun _ -> a) s

(* Create VL lenses for record fields *)
type person = { name : string; age : int }

let vl_name : (person, string) vl_lens = {
  run_identity = (fun f p -> { p with name = f p.name });
  run_const = (fun p -> p.name);
}

let vl_age : (person, int) vl_lens = {
  run_identity = (fun f p -> { p with age = f p.age });
  run_const = (fun p -> p.age);
}

(* Approach 3: Composition of VL lenses *)
let vl_compose (outer : ('s, 'a) vl_lens) (inner : ('a, 'b) vl_lens) : ('s, 'b) vl_lens = {
  run_identity = (fun f s -> outer.run_identity (inner.run_identity f) s);
  run_const = (fun s -> inner.run_const (outer.run_const s));
}

type address = { street : string; city : string }
type employee = { emp_name : string; addr : address }

let vl_addr : (employee, address) vl_lens = {
  run_identity = (fun f e -> { e with addr = f e.addr });
  run_const = (fun e -> e.addr);
}

let vl_city : (address, string) vl_lens = {
  run_identity = (fun f a -> { a with city = f a.city });
  run_const = (fun a -> a.city);
}

let vl_emp_city = vl_compose vl_addr vl_city

(* === Tests === *)
let () =
  let alice = { name = "Alice"; age = 30 } in

  (* VL lens get *)
  assert (vl_view vl_name alice = "Alice");
  assert (vl_view vl_age alice = 30);

  (* VL lens set *)
  let alice2 = vl_set vl_name "Alicia" alice in
  assert (vl_view vl_name alice2 = "Alicia");

  (* VL lens over *)
  let alice3 = vl_over vl_age (fun a -> a + 1) alice in
  assert (vl_view vl_age alice3 = 31);

  (* Composed VL lens *)
  let emp = { emp_name = "Bob"; addr = { street = "123 Main"; city = "NYC" } } in
  assert (vl_view vl_emp_city emp = "NYC");
  let emp2 = vl_set vl_emp_city "LA" emp in
  assert (vl_view vl_emp_city emp2 = "LA");
  let emp3 = vl_over vl_emp_city String.uppercase_ascii emp in
  assert (vl_view vl_emp_city emp3 = "NYC");

  (* Composition is just function composition *)
  let composed_over = vl_over (vl_compose vl_addr vl_city) String.uppercase_ascii emp in
  assert (vl_view vl_emp_city composed_over = "NYC");

  print_endline "โœ“ All tests passed"

๐Ÿ“Š Detailed Comparison

Comparison: Example 212 โ€” Van Laarhoven Lenses

Functor Abstraction

OCaml

๐Ÿช Show OCaml equivalent
module type FUNCTOR = sig
type 'a t
val map : ('a -> 'b) -> 'a t -> 'b t
end

module Identity = struct
type 'a t = 'a
let map f x = f x
end

Rust

trait Functor {
 type Inner;
 type Mapped<B>;
 fn map<B>(self, f: impl FnOnce(Self::Inner) -> B) -> Self::Mapped<B>;
}

struct Identity<A>(A);
impl<A> Functor for Identity<A> {
 type Inner = A;
 type Mapped<B> = Identity<B>;
 fn map<B>(self, f: impl FnOnce(A) -> B) -> Identity<B> { Identity(f(self.0)) }
}

Practical VL Lens

OCaml

๐Ÿช Show OCaml equivalent
type ('s, 'a) vl_lens = {
run_identity : ('a -> 'a) -> 's -> 's;
run_const    : 's -> 'a;
}

let vl_compose outer inner = {
run_identity = (fun f s -> outer.run_identity (inner.run_identity f) s);
run_const = (fun s -> inner.run_const (outer.run_const s));
}

Rust

struct VLLens<S, A> {
 over_fn: Box<dyn Fn(&dyn Fn(&A) -> A, &S) -> S>,
 get_fn: Box<dyn Fn(&S) -> A>,
}

fn compose<B>(self, inner: VLLens<A, B>) -> VLLens<S, B> {
 VLLens {
     over_fn: Box::new(move |f, s| (og)(&|a| (ig)(f, a), s)),
     get_fn: Box::new(move |s| (iget)(&(oget)(s))),
 }
}