Lenses, Prisms, Isos, Folds, and Kaleidescopes

We Need More Optics

Rodolfo Hansen
The Startup
4 min readAug 14, 2020

--

I want to chime in on the sleeping Giant that are Lenses and Prisms in software development:

They are probably the most powerful unused tool in our arsenal!

As a Learning Tool

Optics can be the perfect bridge from Algebraic Data Types to the Functor hierarchies and beyond. They can the prism (could not resist the pun) through which you can peek into useful and concrete applications of more abstract ideas; once you understand the connection between:

  • tuples and functions
  • products and powers
  • conjunction and implication

Optics is a very concrete and visceral connection between computation and logic. The way I think of Optics is a portable way to house the logical connection between two types.

We can ask ourselves how do we encode a biconditional
(A→B ∧ B→A)? E.g. If I have 1 meter then I can get you 3.28084 feet, and if I have 1 feet then I can get you 0.3048 meters. There is a logic interpretation that says, if we have x meters than we can get y feet and vice-versa.

As we grow comfortable with quick exercises around the consequences of these observations like bi-conditional above (A→B ∧ B→A) then it is a very short jump to again seeing it as just a tuple of functions, this all then helps to strengthen intuitions around existence as proof.

We can weaken the statement and say you can’t always go back from B to A; that you need some other A before the information in B can get you all the way around; that statement then is (A→B ∧ (A∧B→A)); and it may seem less useful in logic, but is actually yields very valid operation when exploring the problem from the computational angle. It is also an extremely useful example of highlighting the value of being able to shift back and forth between perspectives.

As you explore more general encodings they visit Predicate Functor Logic’s connection to type classes in Haskell and the notion of Context Bounds in Scala and again help developers find connections between internal computations and the unpredictable effects of interacting with the real world.

Before that, you will notice a Traversal in optics goes from A to B, that is to say one A can go through many Bs… You can start describing your A as a basket with many apples and you can iterate over that basket, and take a bite out of every apple; but you have the concrete specific basket that is A. It is a smooth insertion point to say; well, we can also talk about a List of things, and you get to introduce the Traverse type class, and higher kinded types with enough of a base of concrete examples, you get the opportunity to go back and retrofit the basic optics implementation as profunctors.

Here is a Traversal from a BalanceSheet to every time inside every transaction:

Example of some small functions composing together

It is a very elegant introduction to effects and encourages strong re-use; You are able to describe effectful functors, and aplicatives with a foggy glass metaphor that is susceptible to atmospheric conditions; and are then logical predicates that introduce uncertainty and undecidability to the original logical statements in your clear optics.

Here a pure lens is combined with a database storage effect

Modular Application Design

Because they are a concrete, portable way to relate two types; you can allow one type to vary and replace a direct dependence on a specific data type with an Optic, which can hold the relevant attributes about the required relationship. You are able, for example, to specify if you need the guarantees of an Iso, or the freedom of a Getter. This means interoperability between otherwise wholly independent modules is straight forward and can be done simply at the call site, or automated via some form of code generator in your host language.

We can eliminate the dependence on the BalanceSheet type by requiring a Traversal instead.

Optics are also great at turning a host language’s control flow into just type definitions.

What to do is decided by the effect type: IO, and the machinery of fs2

Leveraging optics helps make your code more reusable; in fact, the clarity of composition lets you write complex ETL or data transformation pipelines by simply composing different optics with the most basic set of actions you want to perform in whatever environmental context you are in.

Re-usable tests

Re-using the existing code from any given Optics library, not only means there is less of your own code to maintain; but it also implies the burden of testing is greatly reduced as the combinators will be already tested in the library itself and you can re-use it’s existing tests on your own particularly defined instances.

You can, for example, run all the tests for a valid traversal on your own custom ones.

--

--

Rodolfo Hansen
The Startup

Constructive Programming Advocate. Looking for new ways to leverage the connection between categories, logic, language, and lambdas…