Introduction

After foraying into mystical lands Functional-programming the last couple of years, learning Haskell a fair bunch, I’ve recently started looking deeply into the fascinating world of Optics - Lenses, Folds, Prisms, Traversals & friends - These make data manipulation easy(ish) and intuitive in Haskell, but they’re low-key infamous for being a little hard-to-read, , the operator notation hard to work with, sometimes, being compared to hieroglyphs (.~ , .^ , .% anyone?) & the no-good long type errors that seem indecipherable. Is that really the case, or are optics just misunderstood? Guess we’ll find that in today’s episode of DRAGON BALL-Z.

I’ve decided to do a series of blog posts about them, tackling the fundamental questions posed above

  • What are they?
  • How do they work & how to use them?
  • Are they really hard (to read & write) or just misunderstood?

i intend to share what I’ve been learning through these blog posts, hoping to make sense of it all - for myself & maybe others - organize my thoughts & learnings, all the while maybe even being of help to someone, if only to my future-self.

What are optics anyway ?

So, What’s the big deal with this Optics stuff anyway, didn’t i study those in physics class?

Well, just like the physical optical lenses & glasses help us focus on details in pictures & things better, the programming optics, help us focus on & navigate real world data (and parts of it) in the world of functional programming.

More formally, optics are a field of study, with composability at it center. Optics, in FP context generally, refer to a family of tools (Lenses, Fold, Traversals, Isos & more). These tools let us focus on and do different things with data. They are interoperable with each another, we can even compose them.

They also lie on a continuous spectrum of constrained <> Flexible, leaning on either side. Constrained optics give us certain guarantees that the more flexible ones can’t (some even have laws they must follow) and are thus easier to reason about in what they must do. The more Flexible optics, as the name suggests, give us more flexibility and let us do things, that their more constrained brethren can’t.

They solve a lot of our data manipulation problems in a concise way while also being performant.

How are optics useful to us?

How do we use these optics thingies so they actually become useful to us instead of just sounding cool & fancy?

We’ll make use of the time-tested pedagogical tool - an example - to understand this:

A Lens, is an optic, that can focus on just one thing - a single piece of data - within a larger structure. When, focused, it allows us to perform actions, just on that piece of data.

consider an actual example

import Control.Lens

view _2 ("Name", "Izuku Midoriya") -- "Izuku Midoriya"
set _2 "Deku" ("Name", "Izuku Midoriya") -- ("Name", "Deku")
  • Lets break that down slowly:
    1. view is the action we perform on the data, focused by the lens.

    2. _2 is the actual lens named _2, because it focuses on the 2nd element of a tuple. This is sort of like the `snd` function.

    3. The String “Izuku Midoriya”, second field of the pair tuple, is the focus here.

    4. (“Name”, “Izuku Midoriya”) - is the larger data-structure, from which, our `_2` lens focuses just on the second part

For a better understanding, annotated examples:


    --> Action
   /
  /    --> Path         --> Focus
 /    /                /
view _2 ("Name", "Izuku Midoriya")
        |                        |
        +------------------------+
              The Structure


    --> Action
   /
  /        --> Path                --> Focus
 /        /                       /
view (_2 . _2) ("Hero", ("Name", "Deku"), ("quirk", "one for all"))
               |                                                  |
               +--------------------------------------------------+
                                The Structure

Major usefulness points

Separation of concerns

Using Lenses (and more generally, optics) acts as an abstraction that promotes separation-of-concerns We have successfully separated out the data that we want to focus on, from the action we want to perform on it.

There are other optics that allow us to focus on more that one thing at a time. we can perform their sum, if they are numbers, then gently swap the sum, for say an average, without ever changing the focus.

Really neat with nested records

Haskell has special syntax to update record fields and syntax doesn’t compose. Updating record fields nested even a couple of level deep (you’d hit level 2 or 3 nesting fairly easily) gets unwieldy & verbose and if you have more nesting, lets just say it just doesn’t spark any joy.

lens make working with nested fields easy as the path can be made with composing smaller optics, that can further compose to help us focus as deep or shallow as we want, with relative ease

Great for interfacing & refactoring

lenses / optics can be used (almost exclusively) as an interface to get, set or modify any record fields. Instead of using or even exposing data type constructors and record field-accessors directly, we expose just the lenses (for eg.) made fromthose fields and the smart constructors to construct those records.

When we need to interface or refactor, we can introduce new lenses that consume existing structure and prevent breakage of any users of existing structure & yet, migrate them to the now new structure by creating optics that automatically convert the old syntax/types to new ones for us.

Virtual fields and invariants

Optics help us create and maintain virtual fields - fields that don’t exists as record fields - these are also known as managed or computed fields in other languages. They help us combine various data into more useful fields for ease of use and more information area.

Optics also helps us maintain invariants between fields, both real & virtual, by using relevant optics tools

Conclusion

These are but a few benefits i could elucidate in this relatively short blog post, but even these seem powerful to merit the study of deep optics on their own. This isn’t an elaborate plot by big-optics to sell more optics.

The optics ecosystem has been around long enough to become mature and most gnarly bugs & performance issues seem ironed out.

We didn’t get into the whole hard-to-read-and-write argument, but that requires a bit more context & understanding, which should follow.

There are some downsides to using optics too, like the added complexity, especially in type-level and the no-good page-long type errors. But those could be learned to be read, managed, yet, even worked with. (NO, that’s not Stockholm syndrome ;) )

  • Strengths - Composeable, Separation of concerns, Mature ecosystem, Performant etc.

  • Weaknesses - Ugly type errors, Complex implementations, the sheer diversity? of optics (sounds like a strength TB) etc.

Hope that was good enough of a overview of whats-what of optics & it’s quotidian terminology.

Other blog posts in this series to follow should go in more detail w.r.t to the respective optic they’re about and how to best work with them.

Write soon,

-Arjun