ExamplesBy LevelBy TopicLearning Paths
239 Expert

Strong Profunctor

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Strong Profunctor" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. A `Strong` profunctor extends a profunctor with `first: P<A, B> -> P<(A, C), (B, C)>` — the ability to "pass along" extra context `C` while the profunctor operates on `A -> B`. Key difference from OCaml: 1. **Lens encoding**: `Strong` captures exactly what makes lenses work; functions are Strong, making the function profunctor the reference implementation.

Tutorial

The Problem

A Strong profunctor extends a profunctor with first: P<A, B> -> P<(A, C), (B, C)> — the ability to "pass along" extra context C while the profunctor operates on A -> B. Functions are Strong: first(f) = |(a, c)| (f(a), c). The significance: in the profunctor optics encoding, lenses are exactly Strong profunctors. A Van Laarhoven lens type Lens s a = ∀p. Strong p => p a b -> p s t works for any Strong profunctor.

🎯 Learning Outcomes

  • • Understand Strong as the profunctor class that captures lens-like behavior
  • • Learn first and second as the two Strong operations
  • • See how functions implement Strong by threading context through
  • • Connect Strong to lens encoding: lenses are polymorphic over all Strong profunctors
  • Code Example

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Key Differences

  • Lens encoding: Strong captures exactly what makes lenses work; functions are Strong, making the function profunctor the reference implementation.
  • **first vs. second**: Both are derivable from each other given swap: second p = dimap swap swap (first p) — only one needs to be primitive.
  • Profunctor class hierarchy: Strong extends Profunctor; Choice extends Profunctor for prisms; their intersection is affine traversals.
  • Type complexity: The profunctor optics encoding requires rank-2 types for the full elegance; Rust and OCaml approximate it.
  • OCaml Approach

    OCaml's Strong profunctor:

    module type STRONG = sig
      include PROFUNCTOR
      val first : ('a, 'b) t -> ('a * 'c, 'b * 'c) t
      val second : ('a, 'b) t -> ('c * 'a, 'c * 'b) t
    end
    

    Lenses in the profunctor encoding: type ('s, 't, 'a, 'b) lens = { run : 'p. (module STRONG with type ('a,'b) t = 'p) -> 'p a b -> 'p s t }. This requires first-class modules and rank-2 types — OCaml handles this more naturally than Rust.

    Full Source

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Deep Comparison

    OCaml vs Rust: Strong Profunctor

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Implement Strong for Parser<A, B> (from example 238) — running a parser while threading context through.
  • Write lens_via_strong(get, set) that creates a lens from get/set pair using the Strong profunctor encoding.
  • Verify that second(f) = dimap(swap, swap)(first(f)) produces the same result as a direct second implementation.
  • Open Source Repos