// 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"