/// Strong Profunctor โ enables lenses.
///
/// A Strong profunctor P<A,B> can "pass along" extra context:
///
/// first :: P A B -> P (A, C) (B, C) -- focus on the first element
/// second :: P A B -> P (C, A) (C, B) -- focus on the second element
///
/// The canonical strong profunctor is functions `fn(A) -> B`.
///
/// Strong profunctors enable the optics hierarchy:
/// Lens s a = โ p. Strong p => p a a -> p s s
///
/// A lens focuses on a part `a` of a whole `s`.
/// It requires `Strong` because we need to "carry the rest of s" through.
use std::rc::Rc;
/// A function-wrapper as the canonical Strong Profunctor.
pub struct Mapper<A, B> {
f: Box<dyn Fn(A) -> B>,
}
impl<A: 'static, B: 'static> Mapper<A, B> {
pub fn new(f: impl Fn(A) -> B + 'static) -> Self {
Mapper { f: Box::new(f) }
}
pub fn apply(&self, a: A) -> B {
(self.f)(a)
}
/// first: lift P(A,B) to P((A,C),(B,C)) โ keep the C component unchanged.
pub fn first<C: 'static>(self) -> Mapper<(A, C), (B, C)> {
let f = self.f;
Mapper::new(move |(a, c)| (f(a), c))
}
/// second: lift P(A,B) to P((C,A),(C,B))
pub fn second<C: 'static>(self) -> Mapper<(C, A), (C, B)> {
let f = self.f;
Mapper::new(move |(c, a)| (c, f(a)))
}
/// dimap for composition
pub fn dimap<C: 'static, D: 'static>(
self,
pre: impl Fn(C) -> A + 'static,
post: impl Fn(B) -> D + 'static,
) -> Mapper<C, D> {
let f = self.f;
Mapper::new(move |c| post(f(pre(c))))
}
}
// โโ Lens โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/// A concrete lens: focuses on part `A` inside whole `S`.
pub struct Lens<S, A> {
pub get: Box<dyn Fn(&S) -> A>,
pub set: Box<dyn Fn(S, A) -> S>,
}
impl<S: Clone + 'static, A: Clone + 'static> Lens<S, A> {
pub fn new(
get: impl Fn(&S) -> A + 'static,
set: impl Fn(S, A) -> S + 'static,
) -> Self {
Lens {
get: Box::new(get),
set: Box::new(set),
}
}
/// view: get the focused value
pub fn view(&self, s: &S) -> A {
(self.get)(s)
}
/// set: replace the focused value
pub fn set_val(&self, s: S, a: A) -> S {
(self.set)(s, a)
}
/// over: modify the focused value
pub fn over(&self, s: S, f: impl Fn(A) -> A) -> S {
let a = (self.get)(&s);
(self.set)(s, f(a))
}
/// Compose two lenses: focus on S -> T -> A
pub fn compose<T: Clone + 'static>(
outer: Lens<S, T>,
inner: Lens<T, A>,
) -> Lens<S, A> {
let outer1 = Rc::new(outer);
let inner1 = Rc::new(inner);
let outer2 = outer1.clone();
let inner2 = inner1.clone();
Lens::new(
move |s| inner1.view(&outer1.view(s)),
move |s, a| {
let t = outer2.view(&s);
let new_t = inner2.set_val(t, a);
outer2.set_val(s, new_t)
},
)
}
}
// โโ Example data structures โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug, Clone, PartialEq)]
struct Person {
name: String,
age: u32,
}
#[derive(Debug, Clone, PartialEq)]
struct Company {
ceo: Person,
revenue: i64,
}
fn main() {
println!("=== Strong Profunctor + Lenses ===\n");
println!("Strong: first :: P A B -> P (A,C) (B,C)");
println!("Enables lenses: focus on parts of a structure.\n");
// Strong profunctor: first and second
let double = Mapper::new(|n: i32| n * 2);
let double_first = double.first::<String>();
let result = double_first.apply((21, "hello".to_string()));
println!("first (double) applied to (21, \"hello\"): {:?}", result);
let add_one = Mapper::new(|n: i32| n + 1);
let add_one_second = add_one.second::<&str>();
let result2 = add_one_second.apply(("world", 41));
println!("second (add_one) applied to (\"world\", 41): {:?}", result2);
// Lenses
println!();
let name_lens: Lens<Person, String> = Lens::new(
|p: &Person| p.name.clone(),
|mut p: Person, n| { p.name = n; p },
);
let age_lens: Lens<Person, u32> = Lens::new(
|p: &Person| p.age,
|mut p: Person, a| { p.age = a; p },
);
let ceo_lens: Lens<Company, Person> = Lens::new(
|c: &Company| c.ceo.clone(),
|mut c: Company, p| { c.ceo = p; c },
);
let p = Person { name: "Alice".to_string(), age: 45 };
let c = Company { ceo: p.clone(), revenue: 1_000_000 };
// view
println!("CEO name: {}", name_lens.view(&c.ceo));
println!("CEO age: {}", age_lens.view(&c.ceo));
// set via lens
let c2 = ceo_lens.set_val(c.clone(), {
name_lens.set_val(c.ceo.clone(), "Bob".to_string())
});
println!("After set CEO name: {}", name_lens.view(&c2.ceo));
// over (modify)
let c3 = ceo_lens.over(c.clone(), |p| age_lens.over(p, |a| a + 1));
println!("After birthday: age = {}", age_lens.view(&c3.ceo));
// Composed lens: Company -> Person -> String
let ceo_name_lens = Lens::compose(
Lens::new(|c: &Company| c.ceo.clone(), |mut c, p| { c.ceo = p; c }),
Lens::new(|p: &Person| p.name.clone(), |mut p, n| { p.name = n; p }),
);
println!("\nComposed lens (company -> ceo -> name): {}", ceo_name_lens.view(&c));
let c4 = ceo_name_lens.set_val(c, "Carol".to_string());
println!("After set via composed lens: {}", ceo_name_lens.view(&c4));
println!();
println!("The `first` operation is the key: it lets a profunctor carry context.");
println!("A lens IS a profunctor transformation: Lens s a = โp. Strong p => p a a -> p s s");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_first() {
let double = Mapper::new(|n: i32| n * 2);
let r = double.first::<&str>().apply((5, "x"));
assert_eq!(r, (10, "x"));
}
#[test]
fn test_second() {
let inc = Mapper::new(|n: i32| n + 10);
let r = inc.second::<&str>().apply(("tag", 5));
assert_eq!(r, ("tag", 15));
}
#[test]
fn test_lens_view_set() {
let p = Person { name: "Alice".to_string(), age: 30 };
let age_lens: Lens<Person, u32> = Lens::new(
|p| p.age,
|mut p, a| { p.age = a; p },
);
assert_eq!(age_lens.view(&p), 30);
let p2 = age_lens.set_val(p, 31);
assert_eq!(age_lens.view(&p2), 31);
}
#[test]
fn test_lens_over() {
let name_lens: Lens<Person, String> = Lens::new(
|p| p.name.clone(),
|mut p, n| { p.name = n; p },
);
let p = Person { name: "alice".to_string(), age: 0 };
let p2 = name_lens.over(p, |n| n.to_uppercase());
assert_eq!(p2.name, "ALICE");
}
#[test]
fn test_composed_lens() {
let ceo_lens = Lens::new(
|c: &Company| c.ceo.clone(),
|mut c, p| { c.ceo = p; c },
);
let name_lens = Lens::new(
|p: &Person| p.name.clone(),
|mut p, n| { p.name = n; p },
);
let ceo_name = Lens::compose(ceo_lens, name_lens);
let c = Company {
ceo: Person { name: "Old".to_string(), age: 50 },
revenue: 0,
};
let c2 = ceo_name.set_val(c, "New".to_string());
assert_eq!(c2.ceo.name, "New");
}
}
(* A strong profunctor can carry extra information through computation.
first :: p a b -> p (a * c) (b * c)
second :: p a b -> p (c * a) (c * b)
Enables building lenses! *)
(* Functions form a strong profunctor *)
let first f (a, c) = (f a, c)
let second f (c, a) = (c, f a)
(* Lens from strong profunctor *)
(* A lens s a = forall p. Strong p => p a a -> p s s *)
(* Simplified: a pair of get/set *)
type ('s, 'a) lens = {
get : 's -> 'a;
set : 'a -> 's -> 's;
}
let view lens s = lens.get s
let over lens f s = lens.set (f (lens.get s)) s
let set lens a s = lens.set a s
(* Compose lenses *)
let compose l1 l2 = {
get = (fun s -> l2.get (l1.get s));
set = (fun a s -> l1.set (l2.set a (l1.get s)) s);
}
type person = { name: string; age: int }
type company = { ceo: person; revenue: int }
let name_lens = { get = (fun p -> p.name); set = (fun n p -> { p with name = n }) }
let ceo_lens = { get = (fun c -> c.ceo); set = (fun p c -> { c with ceo = p }) }
let ceo_name = compose ceo_lens name_lens
let () =
let c = { ceo = { name = "Alice"; age = 45 }; revenue = 1_000_000 } in
Printf.printf "CEO: %s\n" (view ceo_name c);
let c' = set ceo_name "Bob" c in
Printf.printf "New CEO: %s\n" (view ceo_name c');
let c'' = over ceo_lens (fun p -> { p with age = p.age + 1 }) c in
Printf.printf "CEO age after bday: %d\n" c''.ceo.age