Skip to main content

Lens

Struct Lens 

Source
pub struct Lens<S, T, A, B> { /* private fields */ }
Expand description

A van Laarhoven–style lens encoded with getter/setter function pointers.

S — source type, T — modified source type, A — focus type, B — replacement type.

For simple (non-polymorphic) lenses, use SimpleLens.

Implementations§

Source§

impl<S, T, A, B> Lens<S, T, A, B>

Source

pub fn new(getter: fn(&S) -> A, setter: fn(S, B) -> T) -> Lens<S, T, A, B>

Examples found in repository?
examples/domain_model_optics.rs (lines 58-61)
57fn customer_lens() -> SimpleLens<Order, Customer> {
58    Lens::new(
59        |o: &Order| o.customer.clone(),
60        |o, customer| Order { customer, ..o },
61    )
62}
63
64fn address_lens() -> SimpleLens<Customer, Address> {
65    Lens::new(
66        |c: &Customer| c.address.clone(),
67        |c, address| Customer { address, ..c },
68    )
69}
70
71fn city_lens() -> SimpleLens<Address, String> {
72    Lens::new(
73        |a: &Address| a.city.clone(),
74        |a, city| Address { city, ..a },
75    )
76}
77
78fn zip_lens() -> SimpleLens<Address, String> {
79    Lens::new(|a: &Address| a.zip.clone(), |a, zip| Address { zip, ..a })
80}
81
82fn name_lens() -> SimpleLens<Customer, String> {
83    Lens::new(
84        |c: &Customer| c.name.clone(),
85        |c, name| Customer { name, ..c },
86    )
87}
More examples
Hide additional examples
examples/data_transformation.rs (lines 73-76)
72fn amount_lens() -> SimpleLens<Transaction, i64> {
73    Lens::new(
74        |t: &Transaction| t.amount_cents,
75        |t, amount_cents| Transaction { amount_cents, ..t },
76    )
77}
78
79fn name_lens() -> SimpleLens<Transaction, String> {
80    Lens::new(
81        |t: &Transaction| t.name.clone(),
82        |t, name| Transaction { name, ..t },
83    )
84}
Source

pub fn get(&self, s: &S) -> A

Examples found in repository?
examples/domain_model_optics.rs (line 170)
159fn main() {
160    println!("=== Domain Model with Optics Example ===\n");
161
162    let order = sample_order();
163
164    // 1. Lens: get/set/over
165    println!("--- Lens: basic get/set/over ---");
166    let customer = customer_lens();
167    let address = address_lens();
168    let city = city_lens();
169
170    println!("  Customer name: {}", name_lens().get(&order.customer));
171    println!("  City: {}", city.get(&address.get(&customer.get(&order))));
172
173    // 2. Composed Lens: deep access with .then()
174    println!("\n--- ComposedLens: deep access with .then() ---");
175    let order_city = customer_lens().then(address_lens()).then(city_lens());
176    let order_zip = customer_lens().then(address_lens()).then(zip_lens());
177
178    println!("  Order city: {}", order_city.get(&order));
179    println!("  Order zip:  {}", order_zip.get(&order));
180
181    // Deep set
182    let updated = order_city.set(order.clone(), "Shelbyville".into());
183    println!("  After city update: {}", order_city.get(&updated));
184    println!("  Original unchanged: {}", order_city.get(&order));
185
186    // Deep over (modify)
187    let uppercased = order_city.over(order.clone(), |c| c.to_uppercase());
188    println!("  Uppercased city: {}", order_city.get(&uppercased));
189
190    // 3. Lens::transform — reusable update function
191    println!("\n--- Lens::transform — reusable update function ---");
192    let normalize_city: Box<dyn Fn(String) -> String> = Box::new(|c| c.trim().to_uppercase());
193    let normalize_order_city = city_lens().transform::<FnP>(normalize_city);
194    let addr = Address {
195        street: "456 Oak Ave".into(),
196        city: "  new york  ".into(),
197        zip: "10001".into(),
198    };
199    let normalized = normalize_order_city(addr);
200    println!("  Normalized city: {:?}", normalized.city);
201
202    // 4. Prism: sum type focus
203    println!("\n--- Prism: sum type focus ---");
204    let cc = credit_card_prism();
205    let wallet = wallet_prism();
206    let bank = bank_transfer_prism();
207
208    println!("  Credit card last4: {:?}", cc.preview(&order.payment));
209    println!("  Wallet preview:    {:?}", wallet.preview(&order.payment));
210
211    // Prism::review — construct a variant
212    let new_payment = wallet.review(("PayPal".into(), 5000));
213    println!("  New wallet payment: {:?}", new_payment);
214
215    // Prism::over — modify only if matched
216    let updated_payment = cc.over(order.payment.clone(), |(last4, _exp)| {
217        (last4, "01/28".into())
218    });
219    println!("  Updated CC expiry: {:?}", updated_payment);
220
221    // Prism on non-matching variant: passes through unchanged
222    let unchanged = wallet.over(order.payment.clone(), |(prov, bal)| (prov, bal + 1000));
223    println!("  Wallet over on CC (unchanged): {:?}", unchanged);
224
225    // 5. Prism::transform — reusable pattern-matching function
226    println!("\n--- Prism::transform — reusable variant modifier ---");
227    let add_balance: Box<dyn Fn((String, i64)) -> (String, i64)> =
228        Box::new(|(prov, bal)| (prov, bal + 2500));
229    let add_wallet_balance = wallet.transform::<FnP>(add_balance);
230
231    let wallet_payment = Payment::Wallet {
232        provider: "PayPal".into(),
233        balance_cents: 10000,
234    };
235    let topped_up = add_wallet_balance(wallet_payment);
236    println!("  Topped up: {:?}", topped_up);
237
238    // Apply same transform to non-wallet — passes through
239    let still_cc = add_wallet_balance(order.payment.clone());
240    println!("  CC unchanged: {:?}", still_cc);
241
242    // 6. Summary: combine multiple optics
243    println!("\n--- Combining lenses and prisms ---");
244    let orders = vec![
245        sample_order(),
246        Order {
247            id: 1002,
248            customer: Customer {
249                name: "Bob Jones".into(),
250                address: Address {
251                    street: "789 Elm St".into(),
252                    city: "Capital City".into(),
253                    zip: "12345".into(),
254                },
255            },
256            items: vec![Item {
257                name: "Mouse".into(),
258                price_cents: 2999,
259                quantity: 1,
260            }],
261            payment: Payment::Wallet {
262                provider: "Stripe".into(),
263                balance_cents: 50000,
264            },
265        },
266        Order {
267            id: 1003,
268            customer: Customer {
269                name: "Carol White".into(),
270                address: Address {
271                    street: "321 Pine Rd".into(),
272                    city: "Springfield".into(),
273                    zip: "62702".into(),
274                },
275            },
276            items: vec![],
277            payment: Payment::BankTransfer {
278                iban: "DE89370400440532013000".into(),
279            },
280        },
281    ];
282
283    let order_city_lens = customer_lens().then(address_lens()).then(city_lens());
284    println!("  All cities:");
285    for o in &orders {
286        let payment_type = match &o.payment {
287            Payment::CreditCard { .. } => "CC",
288            Payment::BankTransfer { .. } => "Bank",
289            Payment::Wallet { .. } => "Wallet",
290        };
291        println!(
292            "    Order #{}: {} ({}, pays via {})",
293            o.id,
294            order_city_lens.get(o),
295            name_lens().get(&o.customer),
296            payment_type
297        );
298    }
299
300    // Find all bank IBANs using Prism
301    println!("\n  Bank IBANs:");
302    for o in &orders {
303        if let Some(iban) = bank.preview(&o.payment) {
304            println!("    Order #{}: {}", o.id, iban);
305        }
306    }
307}
Source

pub fn set(&self, s: S, b: B) -> T

Source

pub fn then<X, Y>(self, inner: Lens<A, B, X, Y>) -> ComposedLens<S, T, X, Y>
where S: 'static, T: 'static, A: 'static, B: 'static, X: 'static, Y: 'static,

Chain another lens to focus deeper, producing a ComposedLens.

Examples found in repository?
examples/domain_model_optics.rs (line 175)
159fn main() {
160    println!("=== Domain Model with Optics Example ===\n");
161
162    let order = sample_order();
163
164    // 1. Lens: get/set/over
165    println!("--- Lens: basic get/set/over ---");
166    let customer = customer_lens();
167    let address = address_lens();
168    let city = city_lens();
169
170    println!("  Customer name: {}", name_lens().get(&order.customer));
171    println!("  City: {}", city.get(&address.get(&customer.get(&order))));
172
173    // 2. Composed Lens: deep access with .then()
174    println!("\n--- ComposedLens: deep access with .then() ---");
175    let order_city = customer_lens().then(address_lens()).then(city_lens());
176    let order_zip = customer_lens().then(address_lens()).then(zip_lens());
177
178    println!("  Order city: {}", order_city.get(&order));
179    println!("  Order zip:  {}", order_zip.get(&order));
180
181    // Deep set
182    let updated = order_city.set(order.clone(), "Shelbyville".into());
183    println!("  After city update: {}", order_city.get(&updated));
184    println!("  Original unchanged: {}", order_city.get(&order));
185
186    // Deep over (modify)
187    let uppercased = order_city.over(order.clone(), |c| c.to_uppercase());
188    println!("  Uppercased city: {}", order_city.get(&uppercased));
189
190    // 3. Lens::transform — reusable update function
191    println!("\n--- Lens::transform — reusable update function ---");
192    let normalize_city: Box<dyn Fn(String) -> String> = Box::new(|c| c.trim().to_uppercase());
193    let normalize_order_city = city_lens().transform::<FnP>(normalize_city);
194    let addr = Address {
195        street: "456 Oak Ave".into(),
196        city: "  new york  ".into(),
197        zip: "10001".into(),
198    };
199    let normalized = normalize_order_city(addr);
200    println!("  Normalized city: {:?}", normalized.city);
201
202    // 4. Prism: sum type focus
203    println!("\n--- Prism: sum type focus ---");
204    let cc = credit_card_prism();
205    let wallet = wallet_prism();
206    let bank = bank_transfer_prism();
207
208    println!("  Credit card last4: {:?}", cc.preview(&order.payment));
209    println!("  Wallet preview:    {:?}", wallet.preview(&order.payment));
210
211    // Prism::review — construct a variant
212    let new_payment = wallet.review(("PayPal".into(), 5000));
213    println!("  New wallet payment: {:?}", new_payment);
214
215    // Prism::over — modify only if matched
216    let updated_payment = cc.over(order.payment.clone(), |(last4, _exp)| {
217        (last4, "01/28".into())
218    });
219    println!("  Updated CC expiry: {:?}", updated_payment);
220
221    // Prism on non-matching variant: passes through unchanged
222    let unchanged = wallet.over(order.payment.clone(), |(prov, bal)| (prov, bal + 1000));
223    println!("  Wallet over on CC (unchanged): {:?}", unchanged);
224
225    // 5. Prism::transform — reusable pattern-matching function
226    println!("\n--- Prism::transform — reusable variant modifier ---");
227    let add_balance: Box<dyn Fn((String, i64)) -> (String, i64)> =
228        Box::new(|(prov, bal)| (prov, bal + 2500));
229    let add_wallet_balance = wallet.transform::<FnP>(add_balance);
230
231    let wallet_payment = Payment::Wallet {
232        provider: "PayPal".into(),
233        balance_cents: 10000,
234    };
235    let topped_up = add_wallet_balance(wallet_payment);
236    println!("  Topped up: {:?}", topped_up);
237
238    // Apply same transform to non-wallet — passes through
239    let still_cc = add_wallet_balance(order.payment.clone());
240    println!("  CC unchanged: {:?}", still_cc);
241
242    // 6. Summary: combine multiple optics
243    println!("\n--- Combining lenses and prisms ---");
244    let orders = vec![
245        sample_order(),
246        Order {
247            id: 1002,
248            customer: Customer {
249                name: "Bob Jones".into(),
250                address: Address {
251                    street: "789 Elm St".into(),
252                    city: "Capital City".into(),
253                    zip: "12345".into(),
254                },
255            },
256            items: vec![Item {
257                name: "Mouse".into(),
258                price_cents: 2999,
259                quantity: 1,
260            }],
261            payment: Payment::Wallet {
262                provider: "Stripe".into(),
263                balance_cents: 50000,
264            },
265        },
266        Order {
267            id: 1003,
268            customer: Customer {
269                name: "Carol White".into(),
270                address: Address {
271                    street: "321 Pine Rd".into(),
272                    city: "Springfield".into(),
273                    zip: "62702".into(),
274                },
275            },
276            items: vec![],
277            payment: Payment::BankTransfer {
278                iban: "DE89370400440532013000".into(),
279            },
280        },
281    ];
282
283    let order_city_lens = customer_lens().then(address_lens()).then(city_lens());
284    println!("  All cities:");
285    for o in &orders {
286        let payment_type = match &o.payment {
287            Payment::CreditCard { .. } => "CC",
288            Payment::BankTransfer { .. } => "Bank",
289            Payment::Wallet { .. } => "Wallet",
290        };
291        println!(
292            "    Order #{}: {} ({}, pays via {})",
293            o.id,
294            order_city_lens.get(o),
295            name_lens().get(&o.customer),
296            payment_type
297        );
298    }
299
300    // Find all bank IBANs using Prism
301    println!("\n  Bank IBANs:");
302    for o in &orders {
303        if let Some(iban) = bank.preview(&o.payment) {
304            println!("    Order #{}: {}", o.id, iban);
305        }
306    }
307}
Source§

impl<S, T, A, B> Lens<S, T, A, B>

Source

pub fn to_getter(&self) -> Getter<S, A>

Convert to a Getter (read-only, discards setter).

Source

pub fn to_setter(&self) -> Setter<S, T, A, B>
where S: 'static, T: 'static, A: 'static, B: 'static,

Convert to a Setter (modify-only).

Source

pub fn to_traversal(&self) -> Traversal<S, T, A, B>
where S: 'static, T: 'static, A: 'static, B: 'static,

Convert to a Traversal (single-element focus).

Source

pub fn to_fold(&self) -> Fold<S, A>
where S: 'static, A: 'static,

Convert to a Fold (single-element, read-only).

Source§

impl<S, T, A, B> Lens<S, T, A, B>
where S: Clone,

Source

pub fn over(&self, s: S, f: impl FnOnce(A) -> B) -> T

Examples found in repository?
examples/data_transformation.rs (line 92)
89fn apply_discount(transactions: Vec<Transaction>, pct: f64) -> Vec<Transaction> {
90    let lens = amount_lens();
91    VecF::fmap(transactions, |t| {
92        lens.over(t, |a| (a as f64 * (1.0 - pct / 100.0)) as i64)
93    })
94}
95
96/// Normalize names to uppercase.
97fn normalize_names(transactions: Vec<Transaction>) -> Vec<Transaction> {
98    let lens = name_lens();
99    VecF::fmap(transactions, |t| lens.over(t, |n| n.to_uppercase()))
100}
Source

pub fn transform<P>(&self, pab: <P as HKT2>::P<A, B>) -> <P as HKT2>::P<S, T>
where P: Strong, S: 'static, T: 'static, A: 'static, B: 'static,

Profunctor encoding: transform a P<A, B> into a P<S, T> via this lens.

This is the key operation that connects concrete lenses to the profunctor hierarchy. Given any Strong profunctor P and a value pab: P<A, B>, transform produces P<S, T> by:

  1. first(pab) lifts to P<(A, S), (B, S)>
  2. dimap pre-composes with s -> (get(s), s) and post-composes with (b, s) -> set(s, b)
Examples found in repository?
examples/domain_model_optics.rs (line 193)
159fn main() {
160    println!("=== Domain Model with Optics Example ===\n");
161
162    let order = sample_order();
163
164    // 1. Lens: get/set/over
165    println!("--- Lens: basic get/set/over ---");
166    let customer = customer_lens();
167    let address = address_lens();
168    let city = city_lens();
169
170    println!("  Customer name: {}", name_lens().get(&order.customer));
171    println!("  City: {}", city.get(&address.get(&customer.get(&order))));
172
173    // 2. Composed Lens: deep access with .then()
174    println!("\n--- ComposedLens: deep access with .then() ---");
175    let order_city = customer_lens().then(address_lens()).then(city_lens());
176    let order_zip = customer_lens().then(address_lens()).then(zip_lens());
177
178    println!("  Order city: {}", order_city.get(&order));
179    println!("  Order zip:  {}", order_zip.get(&order));
180
181    // Deep set
182    let updated = order_city.set(order.clone(), "Shelbyville".into());
183    println!("  After city update: {}", order_city.get(&updated));
184    println!("  Original unchanged: {}", order_city.get(&order));
185
186    // Deep over (modify)
187    let uppercased = order_city.over(order.clone(), |c| c.to_uppercase());
188    println!("  Uppercased city: {}", order_city.get(&uppercased));
189
190    // 3. Lens::transform — reusable update function
191    println!("\n--- Lens::transform — reusable update function ---");
192    let normalize_city: Box<dyn Fn(String) -> String> = Box::new(|c| c.trim().to_uppercase());
193    let normalize_order_city = city_lens().transform::<FnP>(normalize_city);
194    let addr = Address {
195        street: "456 Oak Ave".into(),
196        city: "  new york  ".into(),
197        zip: "10001".into(),
198    };
199    let normalized = normalize_order_city(addr);
200    println!("  Normalized city: {:?}", normalized.city);
201
202    // 4. Prism: sum type focus
203    println!("\n--- Prism: sum type focus ---");
204    let cc = credit_card_prism();
205    let wallet = wallet_prism();
206    let bank = bank_transfer_prism();
207
208    println!("  Credit card last4: {:?}", cc.preview(&order.payment));
209    println!("  Wallet preview:    {:?}", wallet.preview(&order.payment));
210
211    // Prism::review — construct a variant
212    let new_payment = wallet.review(("PayPal".into(), 5000));
213    println!("  New wallet payment: {:?}", new_payment);
214
215    // Prism::over — modify only if matched
216    let updated_payment = cc.over(order.payment.clone(), |(last4, _exp)| {
217        (last4, "01/28".into())
218    });
219    println!("  Updated CC expiry: {:?}", updated_payment);
220
221    // Prism on non-matching variant: passes through unchanged
222    let unchanged = wallet.over(order.payment.clone(), |(prov, bal)| (prov, bal + 1000));
223    println!("  Wallet over on CC (unchanged): {:?}", unchanged);
224
225    // 5. Prism::transform — reusable pattern-matching function
226    println!("\n--- Prism::transform — reusable variant modifier ---");
227    let add_balance: Box<dyn Fn((String, i64)) -> (String, i64)> =
228        Box::new(|(prov, bal)| (prov, bal + 2500));
229    let add_wallet_balance = wallet.transform::<FnP>(add_balance);
230
231    let wallet_payment = Payment::Wallet {
232        provider: "PayPal".into(),
233        balance_cents: 10000,
234    };
235    let topped_up = add_wallet_balance(wallet_payment);
236    println!("  Topped up: {:?}", topped_up);
237
238    // Apply same transform to non-wallet — passes through
239    let still_cc = add_wallet_balance(order.payment.clone());
240    println!("  CC unchanged: {:?}", still_cc);
241
242    // 6. Summary: combine multiple optics
243    println!("\n--- Combining lenses and prisms ---");
244    let orders = vec![
245        sample_order(),
246        Order {
247            id: 1002,
248            customer: Customer {
249                name: "Bob Jones".into(),
250                address: Address {
251                    street: "789 Elm St".into(),
252                    city: "Capital City".into(),
253                    zip: "12345".into(),
254                },
255            },
256            items: vec![Item {
257                name: "Mouse".into(),
258                price_cents: 2999,
259                quantity: 1,
260            }],
261            payment: Payment::Wallet {
262                provider: "Stripe".into(),
263                balance_cents: 50000,
264            },
265        },
266        Order {
267            id: 1003,
268            customer: Customer {
269                name: "Carol White".into(),
270                address: Address {
271                    street: "321 Pine Rd".into(),
272                    city: "Springfield".into(),
273                    zip: "62702".into(),
274                },
275            },
276            items: vec![],
277            payment: Payment::BankTransfer {
278                iban: "DE89370400440532013000".into(),
279            },
280        },
281    ];
282
283    let order_city_lens = customer_lens().then(address_lens()).then(city_lens());
284    println!("  All cities:");
285    for o in &orders {
286        let payment_type = match &o.payment {
287            Payment::CreditCard { .. } => "CC",
288            Payment::BankTransfer { .. } => "Bank",
289            Payment::Wallet { .. } => "Wallet",
290        };
291        println!(
292            "    Order #{}: {} ({}, pays via {})",
293            o.id,
294            order_city_lens.get(o),
295            name_lens().get(&o.customer),
296            payment_type
297        );
298    }
299
300    // Find all bank IBANs using Prism
301    println!("\n  Bank IBANs:");
302    for o in &orders {
303        if let Some(iban) = bank.preview(&o.payment) {
304            println!("    Order #{}: {}", o.id, iban);
305        }
306    }
307}

Trait Implementations§

Source§

impl<S, T, A, B> Optic for Lens<S, T, A, B>

Auto Trait Implementations§

§

impl<S, T, A, B> Freeze for Lens<S, T, A, B>

§

impl<S, T, A, B> RefUnwindSafe for Lens<S, T, A, B>

§

impl<S, T, A, B> Send for Lens<S, T, A, B>

§

impl<S, T, A, B> Sync for Lens<S, T, A, B>

§

impl<S, T, A, B> Unpin for Lens<S, T, A, B>

§

impl<S, T, A, B> UnsafeUnpin for Lens<S, T, A, B>

§

impl<S, T, A, B> UnwindSafe for Lens<S, T, A, B>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.