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