ExamplesBy LevelBy TopicLearning Paths
212 Expert

Van Laarhoven Lenses

Functional Programming

Tutorial

The Problem

The Van Laarhoven encoding represents all lens operations as a single function type: type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s. Choosing different functors selects different operations: Identity functor gives over, Const r functor gives view. The profound payoff: lens composition is plain function composition (f . g). No special composition operator is needed. This is why Haskell's lens library can compose optics with (.).

🎯 Learning Outcomes

  • • Understand the Van Laarhoven encoding: one function type for all lens operations
  • • Learn how Identity and Const functors select different lens operations
  • • See why composition is plain function composition in this encoding
  • • Appreciate the mathematical elegance and understand the limitation in Rust (no rank-2 types)
  • Code Example

    use std::rc::Rc;
    
    // Type aliases make the functor applications readable
    type IdentityApp<S, A> = Rc<dyn Fn(Rc<dyn Fn(A) -> A>) -> Rc<dyn Fn(S) -> S>>;
    type ConstApp<S, A>    = Rc<dyn Fn(&S) -> A>;
    
    pub struct VLLens<S: 'static, A: 'static> {
        apply_identity: IdentityApp<S, A>,  // for over/set
        apply_const:    ConstApp<S, A>,     // for view
    }
    
    impl<S: 'static, A: 'static> VLLens<S, A> {
        pub fn view(&self, s: &S) -> A {
            (self.apply_const)(s)
        }
    
        pub fn over(&self, s: S, f: impl Fn(A) -> A + 'static) -> S {
            let modifier = (self.apply_identity)(Rc::new(f));
            modifier(s)
        }
    }

    Key Differences

  • Rank-2 requirement: Full Van Laarhoven requires rank-2 polymorphism; Rust and OCaml approximate it; Haskell supports it natively.
  • Composition: The central benefit — composition as function composition — is preserved in both the Rust and OCaml simulations.
  • Functor selection: Haskell uses type class resolution to select Identity vs. Const; Rust and OCaml bundle both explicitly in the lens struct.
  • Practical use: Rust uses the simple get/set lens (examples 202-205) in production; Van Laarhoven is studied for its mathematical elegance.
  • OCaml Approach

    OCaml simulates Van Laarhoven more faithfully using rank-2 record polymorphism:

    type ('s, 'a) lens = {
      runLens : 'f. (module FUNCTOR with type 'a t = 'f) -> ('a -> 'f) -> 's -> 'f
    }
    

    This requires first-class modules for the functor parameter. Haskell's lens library uses type class constraints for zero overhead. Neither OCaml nor Rust achieves Haskell's full ergonomics for Van Laarhoven lenses.

    Full Source

    #![allow(clippy::all)]
    //! # Example 212: Van Laarhoven Lenses
    //!
    //! The Van Laarhoven encoding collapses all lens operations into one function type:
    //!   `type Lens s a = ∀f. Functor f ⇒ (a → f a) → s → f s`
    //!
    //! Choosing the functor selects the operation:
    //! - `f = Identity` → `over` (modify the focused field)
    //! - `f = Const r`  → `view` (extract the focused field)
    //!
    //! The deeper payoff: **composition is ordinary function composition**.
    //! In Haskell, `lens_b_in_a . lens_a_in_s` is a valid composed lens.
    //! Rust lacks rank-2 types, so we specialise to Identity and Const and
    //! bundle them into a struct — but the composition logic is identical.
    
    use std::marker::PhantomData;
    use std::rc::Rc;
    
    // ============================================================================
    // Functors
    // ============================================================================
    
    /// Identity functor. `fmap f (Identity x) = Identity (f x)`.
    ///
    /// Plugging Identity into a VL lens gives `over`: the lens applies `f` to
    /// the focused element and returns the updated structure.
    pub struct Identity<A>(pub A);
    
    impl<A> Identity<A> {
        pub fn run(self) -> A {
            self.0
        }
    
        pub fn map<B>(self, f: impl FnOnce(A) -> B) -> Identity<B> {
            Identity(f(self.0))
        }
    }
    
    /// Const functor. `fmap _ (Const r) = Const r` — the payload is ignored.
    ///
    /// Plugging `Const` into a VL lens gives `view`: the lens feeds the focused
    /// element to `f` (which returns `Const r`), and the functor laws propagate
    /// `r` out through the structure without touching it.
    pub struct Const<R, B>(pub R, PhantomData<B>);
    
    impl<R, B> Const<R, B> {
        pub fn new(r: R) -> Self {
            Const(r, PhantomData)
        }
    
        pub fn run(self) -> R {
            self.0
        }
    
        #[allow(clippy::needless_pass_by_value)]
        pub fn map<C>(self, _f: impl FnOnce(B) -> C) -> Const<R, C> {
            Const(self.0, PhantomData)
        }
    }
    
    // ============================================================================
    // Van Laarhoven Lens
    // ============================================================================
    
    /// Lifts a modifier `a → a` to a structure modifier `s → s` (Identity functor).
    /// Type alias to keep the struct definition readable.
    type IdentityApp<S, A> = Rc<dyn Fn(Rc<dyn Fn(A) -> A>) -> Rc<dyn Fn(S) -> S>>;
    
    /// Reads the focused value out of a structure (Const functor).
    type ConstApp<S, A> = Rc<dyn Fn(&S) -> A>;
    
    /// A Van Laarhoven lens focusing on a field of type `A` inside structure `S`.
    ///
    /// In Haskell this is a single rank-2 polymorphic function:
    /// ```text
    /// type Lens s a = ∀f. Functor f ⇒ (a → f a) → s → f s
    /// ```
    ///
    /// Since Rust lacks `∀f`, we represent the two relevant specialisations
    /// explicitly:
    /// - `apply_identity` — the Identity functor application  → `over`/`set`
    /// - `apply_const`    — the Const functor application     → `view`
    ///
    /// ### Composition
    /// The VL payoff: composing two lenses mirrors Haskell's `(.)`:
    /// ```text
    /// (outer . inner).apply(f) = outer.apply(inner.apply(f))
    /// ```
    /// One lens is simply passed as an argument to the other's `apply` —
    /// no special `compose_lens` combinator is required.
    pub struct VLLens<S: 'static, A: 'static> {
        /// Identity specialisation: lifts `a → a` to `s → s`.
        apply_identity: IdentityApp<S, A>,
    
        /// Const specialisation: reads the focused value out of `s`.
        apply_const: ConstApp<S, A>,
    }
    
    impl<S: 'static, A: 'static> VLLens<S, A> {
        /// Build a VL lens from a `getter` and a consuming `setter`.
        pub fn new(getter: impl Fn(&S) -> A + 'static, setter: impl Fn(S, A) -> S + 'static) -> Self {
            let getter = Rc::new(getter);
            let setter = Rc::new(setter);
    
            let getter_id = Rc::clone(&getter);
            let setter_id = Rc::clone(&setter);
    
            VLLens {
                // Identity application: given modifier `f: a → a`, produce `s → s`
                // In Haskell: `λf s → run_identity (l (Identity . f) s)`
                apply_identity: Rc::new(move |f: Rc<dyn Fn(A) -> A>| {
                    let g = Rc::clone(&getter_id);
                    let s = Rc::clone(&setter_id);
                    let f = Rc::clone(&f);
                    Rc::new(move |structure: S| {
                        let a = g(&structure);
                        let a2 = f(a);
                        s(structure, a2)
                    }) as Rc<dyn Fn(S) -> S>
                }),
                // Const application: given `s`, extract the focused `a`
                // In Haskell: `λs → get_const (l Const s)`
                apply_const: Rc::new(move |structure: &S| getter(structure)),
            }
        }
    
        /// Extract the focused value from `s`.
        ///
        /// Corresponds to the Const functor application:
        /// `view l s = get_const (l Const s)`
        pub fn view(&self, s: &S) -> A {
            (self.apply_const)(s)
        }
    
        /// Apply a pure function to the focused value and return the updated structure.
        ///
        /// Corresponds to the Identity functor application:
        /// `over l f s = run_identity (l (Identity . f) s)`
        pub fn over(&self, s: S, f: impl Fn(A) -> A + 'static) -> S {
            let modifier = (self.apply_identity)(Rc::new(f));
            modifier(s)
        }
    
        /// Replace the focused value with `a`.
        pub fn set(&self, s: S, a: A) -> S
        where
            A: Clone,
        {
            self.over(s, move |_| a.clone())
        }
    
        /// Compose two VL lenses.
        ///
        /// `self` focuses `S → A`; `inner` focuses `A → B`.
        /// Result focuses `S → B`.
        ///
        /// The implementation mirrors Haskell's `(.)`:
        /// ```text
        /// (outer . inner).apply(f) = outer.apply(inner.apply(f))
        /// ```
        /// This is plain function application — the same formula as `(.)` in Haskell.
        pub fn compose<B: 'static>(self, inner: VLLens<A, B>) -> VLLens<S, B> {
            let outer_id = self.apply_identity;
            let inner_id = inner.apply_identity;
            let outer_c = self.apply_const;
            let inner_c = inner.apply_const;
    
            VLLens {
                // Composition = outer.apply(inner.apply(f))
                // This is function composition: outer ∘ inner
                apply_identity: Rc::new(move |f: Rc<dyn Fn(B) -> B>| {
                    let inner_lifted: Rc<dyn Fn(A) -> A> = (inner_id)(f);
                    (outer_id)(inner_lifted)
                }),
                apply_const: Rc::new(move |s: &S| {
                    let a = (outer_c)(s);
                    (inner_c)(&a)
                }),
            }
        }
    }
    
    // ============================================================================
    // Domain types
    // ============================================================================
    
    #[derive(Clone, Debug, PartialEq)]
    pub struct Address {
        pub street: String,
        pub city: String,
    }
    
    #[derive(Clone, Debug, PartialEq)]
    pub struct Person {
        pub name: String,
        pub age: u32,
        pub address: Address,
    }
    
    // ============================================================================
    // Lenses for the domain
    // ============================================================================
    
    pub fn person_name() -> VLLens<Person, String> {
        VLLens::new(|p: &Person| p.name.clone(), |p, name| Person { name, ..p })
    }
    
    pub fn person_age() -> VLLens<Person, u32> {
        VLLens::new(|p: &Person| p.age, |p, age| Person { age, ..p })
    }
    
    pub fn person_address() -> VLLens<Person, Address> {
        VLLens::new(
            |p: &Person| p.address.clone(),
            |p, address| Person { address, ..p },
        )
    }
    
    pub fn address_city() -> VLLens<Address, String> {
        VLLens::new(
            |a: &Address| a.city.clone(),
            |a, city| Address { city, ..a },
        )
    }
    
    // ============================================================================
    // Tests
    // ============================================================================
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        fn alice() -> Person {
            Person {
                name: "Alice".to_string(),
                age: 30,
                address: Address {
                    street: "1 Elm St".to_string(),
                    city: "Springfield".to_string(),
                },
            }
        }
    
        // --- view ---
    
        #[test]
        fn test_view_simple_field() {
            let p = alice();
            assert_eq!(person_age().view(&p), 30);
        }
    
        #[test]
        fn test_view_string_field() {
            let p = alice();
            assert_eq!(person_name().view(&p), "Alice");
        }
    
        #[test]
        fn test_view_nested_via_compose() {
            // Compose person_address and address_city to reach Person → city
            let person_city = person_address().compose(address_city());
            let p = alice();
            assert_eq!(person_city.view(&p), "Springfield");
        }
    
        // --- over ---
    
        #[test]
        fn test_over_increments_age() {
            let p = alice();
            let updated = person_age().over(p, |age| age + 1);
            assert_eq!(updated.age, 31);
            assert_eq!(updated.name, "Alice"); // other fields unchanged
        }
    
        #[test]
        fn test_over_uppercases_name() {
            let p = alice();
            let updated = person_name().over(p, |n| n.to_uppercase());
            assert_eq!(updated.name, "ALICE");
            assert_eq!(updated.age, 30);
        }
    
        #[test]
        fn test_over_composed_lens_modifies_nested_field() {
            // Compose to get Person → city, then over to upper-case it
            let person_city = person_address().compose(address_city());
            let p = alice();
            let updated = person_city.over(p, |c| c.to_uppercase());
            assert_eq!(updated.address.city, "SPRINGFIELD");
            assert_eq!(updated.address.street, "1 Elm St"); // sibling unchanged
            assert_eq!(updated.name, "Alice"); // parent unchanged
        }
    
        // --- set ---
    
        #[test]
        fn test_set_replaces_age() {
            let p = alice();
            let updated = person_age().set(p, 99);
            assert_eq!(updated.age, 99);
        }
    
        #[test]
        fn test_set_composed_lens_replaces_city() {
            let person_city = person_address().compose(address_city());
            let p = alice();
            let updated = person_city.set(p, "Shelbyville".to_string());
            assert_eq!(updated.address.city, "Shelbyville");
            assert_eq!(updated.address.street, "1 Elm St");
            assert_eq!(updated.age, 30);
        }
    
        // --- identity / immutability laws ---
    
        #[test]
        fn test_original_not_mutated() {
            let p = alice();
            let _updated = person_age().over(p.clone(), |a| a + 100);
            // p.clone() is updated; original p is still 30
            assert_eq!(p.age, 30);
        }
    
        #[test]
        fn test_over_with_identity_function_preserves_structure() {
            let p = alice();
            let updated = person_age().over(p.clone(), |a| a);
            assert_eq!(updated, p);
        }
    
        // --- VL insight: composition = function composition ---
    
        #[test]
        fn test_composition_is_function_application() {
            // The VL composition formula:
            //   (outer.compose(inner)).apply(f) == outer.apply(inner.apply(f))
            //
            // We verify both paths produce the same result.
            let f: Rc<dyn Fn(String) -> String> = Rc::new(|c: String| c.to_uppercase());
    
            // Path A: compose lenses, then apply
            let person_city = person_address().compose(address_city());
            let result_a = person_city.over(alice(), |c| c.to_uppercase());
    
            // Path B: apply inner lens first, then outer lens (manual function composition)
            let inner_lifted = address_city().apply_identity.clone();
            let inner_fn: Rc<dyn Fn(Address) -> Address> = (inner_lifted)(f.clone());
            let outer_lifted = person_address().apply_identity.clone();
            let outer_fn: Rc<dyn Fn(Person) -> Person> = (outer_lifted)(inner_fn);
            let result_b = outer_fn(alice());
    
            assert_eq!(result_a, result_b);
        }
    
        // --- functor types ---
    
        #[test]
        fn test_identity_functor_map() {
            let id = Identity(5i32);
            let mapped = id.map(|x| x * 2);
            assert_eq!(mapped.run(), 10);
        }
    
        #[test]
        fn test_const_functor_map_ignores_function() {
            let c: Const<i32, String> = Const::new(42);
            let mapped: Const<i32, bool> = c.map(|_s: String| true);
            assert_eq!(mapped.run(), 42); // payload preserved, phantom type changed
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        fn alice() -> Person {
            Person {
                name: "Alice".to_string(),
                age: 30,
                address: Address {
                    street: "1 Elm St".to_string(),
                    city: "Springfield".to_string(),
                },
            }
        }
    
        // --- view ---
    
        #[test]
        fn test_view_simple_field() {
            let p = alice();
            assert_eq!(person_age().view(&p), 30);
        }
    
        #[test]
        fn test_view_string_field() {
            let p = alice();
            assert_eq!(person_name().view(&p), "Alice");
        }
    
        #[test]
        fn test_view_nested_via_compose() {
            // Compose person_address and address_city to reach Person → city
            let person_city = person_address().compose(address_city());
            let p = alice();
            assert_eq!(person_city.view(&p), "Springfield");
        }
    
        // --- over ---
    
        #[test]
        fn test_over_increments_age() {
            let p = alice();
            let updated = person_age().over(p, |age| age + 1);
            assert_eq!(updated.age, 31);
            assert_eq!(updated.name, "Alice"); // other fields unchanged
        }
    
        #[test]
        fn test_over_uppercases_name() {
            let p = alice();
            let updated = person_name().over(p, |n| n.to_uppercase());
            assert_eq!(updated.name, "ALICE");
            assert_eq!(updated.age, 30);
        }
    
        #[test]
        fn test_over_composed_lens_modifies_nested_field() {
            // Compose to get Person → city, then over to upper-case it
            let person_city = person_address().compose(address_city());
            let p = alice();
            let updated = person_city.over(p, |c| c.to_uppercase());
            assert_eq!(updated.address.city, "SPRINGFIELD");
            assert_eq!(updated.address.street, "1 Elm St"); // sibling unchanged
            assert_eq!(updated.name, "Alice"); // parent unchanged
        }
    
        // --- set ---
    
        #[test]
        fn test_set_replaces_age() {
            let p = alice();
            let updated = person_age().set(p, 99);
            assert_eq!(updated.age, 99);
        }
    
        #[test]
        fn test_set_composed_lens_replaces_city() {
            let person_city = person_address().compose(address_city());
            let p = alice();
            let updated = person_city.set(p, "Shelbyville".to_string());
            assert_eq!(updated.address.city, "Shelbyville");
            assert_eq!(updated.address.street, "1 Elm St");
            assert_eq!(updated.age, 30);
        }
    
        // --- identity / immutability laws ---
    
        #[test]
        fn test_original_not_mutated() {
            let p = alice();
            let _updated = person_age().over(p.clone(), |a| a + 100);
            // p.clone() is updated; original p is still 30
            assert_eq!(p.age, 30);
        }
    
        #[test]
        fn test_over_with_identity_function_preserves_structure() {
            let p = alice();
            let updated = person_age().over(p.clone(), |a| a);
            assert_eq!(updated, p);
        }
    
        // --- VL insight: composition = function composition ---
    
        #[test]
        fn test_composition_is_function_application() {
            // The VL composition formula:
            //   (outer.compose(inner)).apply(f) == outer.apply(inner.apply(f))
            //
            // We verify both paths produce the same result.
            let f: Rc<dyn Fn(String) -> String> = Rc::new(|c: String| c.to_uppercase());
    
            // Path A: compose lenses, then apply
            let person_city = person_address().compose(address_city());
            let result_a = person_city.over(alice(), |c| c.to_uppercase());
    
            // Path B: apply inner lens first, then outer lens (manual function composition)
            let inner_lifted = address_city().apply_identity.clone();
            let inner_fn: Rc<dyn Fn(Address) -> Address> = (inner_lifted)(f.clone());
            let outer_lifted = person_address().apply_identity.clone();
            let outer_fn: Rc<dyn Fn(Person) -> Person> = (outer_lifted)(inner_fn);
            let result_b = outer_fn(alice());
    
            assert_eq!(result_a, result_b);
        }
    
        // --- functor types ---
    
        #[test]
        fn test_identity_functor_map() {
            let id = Identity(5i32);
            let mapped = id.map(|x| x * 2);
            assert_eq!(mapped.run(), 10);
        }
    
        #[test]
        fn test_const_functor_map_ignores_function() {
            let c: Const<i32, String> = Const::new(42);
            let mapped: Const<i32, bool> = c.map(|_s: String| true);
            assert_eq!(mapped.run(), 42); // payload preserved, phantom type changed
        }
    }

    Deep Comparison

    OCaml vs Rust: Van Laarhoven Lenses (Example 212)

    The Core Idea

    A Van Laarhoven lens is a single polymorphic function that unifies get, set, and modify by being abstract over the functor f:

    type Lens s a = ∀f. Functor f ⇒ (a → f a) → s → f s
    

    Plugging in f = Identity gives you over (modify). Plugging in f = Const r gives you view (read). The same function expression handles every operation.


    Side-by-Side Code

    OCaml (module-based encoding)

    (* OCaml can express the polymorphism through first-class modules *)
    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
      let run x = x
    end
    
    module Const (M : sig type t end) = struct
      type 'a t = M.t
      let map _f x = x
      let run x = x
    end
    
    (* A concrete VL lens for a record field *)
    let person_age_lens (module F : FUNCTOR) f p =
      F.map (fun age -> { p with age }) (f p.age)
    
    (* view: plug in Const *)
    let view lens s =
      let module C = Const(struct type t = _ end) in
      C.run (lens (module C) (fun a -> C.map (fun _ -> a) (C.run ())) s)
    
    (* Composition = function composition *)
    let compose outer inner (module F : FUNCTOR) f s =
      outer (module F) (inner (module F) f) s
    

    Rust (trait-based encoding with two specialisations)

    use std::rc::Rc;
    
    // Type aliases make the functor applications readable
    type IdentityApp<S, A> = Rc<dyn Fn(Rc<dyn Fn(A) -> A>) -> Rc<dyn Fn(S) -> S>>;
    type ConstApp<S, A>    = Rc<dyn Fn(&S) -> A>;
    
    pub struct VLLens<S: 'static, A: 'static> {
        apply_identity: IdentityApp<S, A>,  // for over/set
        apply_const:    ConstApp<S, A>,     // for view
    }
    
    impl<S: 'static, A: 'static> VLLens<S, A> {
        pub fn view(&self, s: &S) -> A {
            (self.apply_const)(s)
        }
    
        pub fn over(&self, s: S, f: impl Fn(A) -> A + 'static) -> S {
            let modifier = (self.apply_identity)(Rc::new(f));
            modifier(s)
        }
    }
    

    Rust (composition — mirrors Haskell's .)

    impl<S: 'static, A: 'static> VLLens<S, A> {
        pub fn compose<B: 'static>(self, inner: VLLens<A, B>) -> VLLens<S, B> {
            let outer_id = self.apply_identity;
            let inner_id = inner.apply_identity;
            let outer_c  = self.apply_const;
            let inner_c  = inner.apply_const;
    
            VLLens {
                // This IS function composition: outer(inner(f))
                apply_identity: Rc::new(move |f| {
                    let inner_lifted = (inner_id)(f);   // (B→B) → (A→A)
                    (outer_id)(inner_lifted)             // (A→A) → (S→S)
                }),
                apply_const: Rc::new(move |s| {
                    let a = (outer_c)(s);               // S → A
                    (inner_c)(&a)                       // A → B
                }),
            }
        }
    }
    

    Type Signatures

    ConceptOCamlRust
    Lens type∀f. Functor f ⇒ (a → f a) → s → f sstruct VLLens<S, A> with two Rc<dyn Fn> fields
    Identity functormodule Identity : FUNCTOR with type 'a t = 'astruct Identity<A>(A)
    Const functormodule Const(M) : FUNCTOR with type 'a t = M.tstruct Const<R, B>(R, PhantomData<B>)
    Compositionfun f s -> outer (inner f) s — plain (.)compose method; body is identical in structure
    Rank-2 polyNative: ∀f. in the typeNot native; split into two fields

    Key Insights

  • OCaml's first-class modules are the key difference.
  • OCaml can pass (module F : FUNCTOR) as a value at runtime, so a single function body handles every functor. Rust has no equivalent; we must split the single polymorphic function into two monomorphised fields.

  • Composition is function composition in both languages.
  • Whether you write outer . inner in Haskell/OCaml or Rc::new(|f| outer_id(inner_id(f))) in Rust, the structure is identical: the outer lens's apply receives the inner lens's apply as its argument. No special compose_lens combinator is needed — or exists — in either encoding.

  • The functor determines the operation.
  • Identity carries the modified value forward; Const ignores updates and propagates a read value. This duality is what lets one lens type expression unify get, set, and modify with no branching on the operation.

  • **'static bounds are the Rust cost of erasing the functor.**
  • Because apply_identity stores a dyn Fn behind Rc, the closures passed to over must be 'static. OCaml has no such restriction because functors are passed as arguments, not stored in trait objects.

  • **This is why Haskell's lens library composes with (.).**
  • Every operator in that library (^., %~, .~, ^..) is just a different functor plugged into the same lens value. Understanding the VL encoding unlocks the entire optics hierarchy: Prism, Traversal, Iso, and Affine all follow the same pattern with different functor constraints.


    When to Use Each Style

    **Use idiomatic Rust VLLens when:** you want free lens composition with the same call-site ergonomics as view/over/set, and you can tolerate 'static bounds on modifier closures. This is the closest Rust gets to the Haskell lens experience.

    **Use the simpler Lens struct (Example 205) when:** you only need one or two levels of composition and prefer avoiding Rc<dyn Fn> overhead. The standard { get, set } pair composes via explicit wiring but incurs no dynamic dispatch on the lens operations themselves.

    Exercises

  • Implement composition for VLLens and verify it produces correct view and over results for a three-level nested structure.
  • Show that view(compose(l1, l2), s) == l1.view(l2.view(s)) for concrete lens examples.
  • Implement identity_lens: VLLens<A, A> where view returns the value itself and over applies the function directly.
  • Open Source Repos