In this post I’d like to try to discuss what functional optics are, without going too much into why they are so cool, and you should use them, or how they are implemented and should be used with a specific language and library. I personally think that functional optics should be a really easy concept to grasp, but currently learning them is harder than it should be mostly due to library implementation details, quite obscure documentation and an exotic usage of weird symbols. Since a picture is worth a thousand words, I will introduce and use a graphical notation to illustrate the concepts we will discuss. Types and values Let’s start introducing our graphical notation from its basic building blocks. We can represent a type with a simple coloured rectangle A value for a given type will be represented as a horizontal line spanning the width of the rectangle Sums and products When considering algebraic data types, we have two ways of combining types, using products and sums. The product of two types A and B is a new type, which we will denote by A*B , whose values are composed of a value of type A and a value of type B . An example of a product type is a tuple like (Int, String) where each value is pair composed of an integer and a string. Graphically we can represent a product type as two side by side rectangles When it comes to values, we need to upgrade a little bit our graphical interpretation. Since a value in a product type is composed of values of its components, we will just represent it as piecewise horizontal line, composed by horizontal lines (possible at different heights) spanning its horizontal sub-rectangles. On the other hand, the sum of two types is represented by two rectangles one on top of the other A value of a sum type is a horizontal line spanning the width of the whole rectangle. If it is a horizontal line in the top rectangle, it means that we are selecting the first type, and we’re using one of its values. If it is a horizontal line in the bottom rectangle, it means that we are selecting the second type and one of its values. More generally, for any algebraic data type, we can represent it as a sum of products by stacking a series of rectangles one on top of the other, each one potentially divided horizontally in multiple sub-rectangles. In general, we can continue to split any sub-rectangle horizontally or vertically (if you prefer a top-down point of view) or you can place two rectangles side by side or top to bottom. A value of such a type is a piecewise horizontal line which can not cross a horizontal division. Optics Now that we have this graphical representation to represent data types, we can use it to discuss various kinds of optics. In general, we can think of an optic as a way to select, given our graphical representation of a type, one (or more) rectangle inside a given rectangle representing a type. For example in the following picture we are selecting the rectangle with the red boundary inside the main rectangle representing a complex type. If call the main type A and the selected type B , we will denote the optic selecting B inside A with Optic A B . Before going into inspecting the various kinds of optics, let’s try to see if can can already derive some properties of optics just by looking at their graphical representation. Compositionality One thing that we can notice is that optics compose really well. Suppose we have a type A represented by the following diagram We can first select a sub-rectangle identifying a type B with an Optic A B . Starting now with the type B we can use an Optic B C to select a type C inside B . Using now the Optic A B and the Optic B C we just chose, we can compose them to obtain an Optic A C which directly selects C inside A . This optic composition operation is actually associative and has an identity element, turning optics into a Category. Let’s now start to have a look at some specific families of optics. Iso The simplest optic we can define for any type A is the one that we can obtain by selecting the whole rectangle. With such a selection we can see that for any value of the outer type A , we actually have a value of the type identified by the red rectangle, which we will call B . This means that, given an Iso A B , we can actually define a function view :: A -> B that for any value of A gives us a value of B . But in this special case also the converse holds! For any value of B , since B is actually A itself, we have in fact a value of A . This gives rise to a function review :: B -> A . In fact review . view = id_A and view . review = id_B giving rise to a proper isomorphism. Lens In our graphical representation, a Lens is a vertical slice of the main rectangle. Any vertical slice cuts out a piece out of any horizontal line. In other terms, given a value of the type A represented by the main rectangle, we have a way to obtain a value of type B represented by our vertical slice. This means that also in this case we are able to define a function view :: A -> B which allows us to focus from the main type to one of its component. On the other hand, it’s not possible with Lens es as it was with Iso s to build back a value of type A from a value of type B , since a value of type B is only a part of value of type A . What is actually possible, though, is to update only the part included in the red rectangle of a value of type A . In other terms, given a Lens A B , we can define a function set :: B -> A -> A which takes a value of type B and a value of type A and updates the section of the latter identified by the Lens . Having a look at the graphical representations of the view and set functions, we can convince ourselves that the following properties hold: If we set a value and then we view it, we must get back what we put in: view (set b a) == b . a value and then we it, we must get back what we put in: . If we set what we get out of a view , nothing changes: set (view a) a == a . what we get out of a , nothing changes: . Setting a value twice is the same thing as setting it once: set b (set b a) == set b a . Moreover, we can notice that composing two lenses with the operation described in the Compositionality section gives us back another lens. A vertical slice of a vertical slice is in fact still a vertical slice of the original rectangle. In other terms this means that Lens es form a subcategory of the bigger category of Optic s. Composing adequately set and view we can also define a function over :: (B -> B) -> A -> A as over f a = set (f $ view a) a . This means that if we have a function f :: B -> B which can transform values of type B , we can use our lens to extract a B from an A via view , use f to transform the result, and eventually use set to update the B part inside the original A . Prism If vertical slices are Lens es, it is only natural to wonder what are horizontal slices. They correspond to Prism s, and they are the dual concept of Lens es. Where a Lens represents a component in a product type, a Prism represents a component in a sum type. Looking at values, we can notice that a value in the main type could either be a value of the inner type or it could be completely outside of it. This implies that, given a Prism A B , we can define a function preview :: A -> Maybe B which, given a value a :: A returns a Just b if a was inside the sub-rectangle identified by B and Nothing otherwise. On the other hard, since a Prism constitutes a horizontal slice of the main rectangle, if we have a value of the sub-rectangle, we can always interpret it a value of the main rectangle. In other words, this means that for a Prism A B we can always define a function review :: B -> A constructing a value of type A from a value of type B . Again, having a look at the graphical representation we can convince ourselves that the following properties hold for Prism s: If we preview through a Prism what we just built using the same Prism , we will get a value back: preview (review b) == Just b . what we just built using the same , we will get a value back: . If when we preview we get a Just , then reviewing the result through the same Prism will get us to the initial value: preview s == Just a => review a == s For a Prism A B it is also possible to define a function set :: B -> A -> A as set = flip $ const review . This means that, being able to construct an A from a B , we are able to substitute a B inside an A just by discarding the initial A and building a new one from B . Graphically, we can interpret this as using the B value in the inner rectangle to build an A value, forgetting about the initial A value. At this point we can also define another function over :: (B -> B) -> A -> A which allows us to update the B part inside an A . We can define it as over f a = maybe a review (f <$> preview a) . In words, we use preview to get a Maybe B and we map f over it to get another Maybe B ; if we have a value Just b , then we can use it to construct an A using review ; on the other hand, if we ended up with a Nothing , we just keep the initial A . Graphically, we can interpret this as follows: if the A value is inside the B sub-rectangle, we apply f and then we use the result to build a new A value; if the value is not in B , we just leave it alone. Looking at the graphical interpretation, it’s easy to convince ourselves that the composition of two Prism s is still a Prism , given that a horizontal slice of a horizontal slice is still a horizontal slice of the main rectangle. In other terms, also Prism s form a subcategory of the category of Optic s. Affine traversals Now that we discussed Lens es and Prism s, one natural question which might arise is what happens when we try to compose a Lens and a Prism . In the picture above we see a Lens (the blue rectangle) composed with a Prism (the red rectangle). What we get out of the composition is the lower right rectangle, which is neither a Lens , nor a Prism , with respect to the main rectangle. It’s just a single inner rectangle. On the other hand, if you think about it, every inner rectangle of the main rectangle could be obtained by composing Lens es and Prism s. An Optic identifying an inner rectangle is called an AffineTraversal . Combining the intuitions we had for Lens es and Prism s, it’s actually possible to define functions set :: B -> A -> A and over :: (B -> B) -> A -> A also for AffineTraversal s. Moreover, the graphical representation suggests us that also AffineTraversal s for a subcategory of Optic s, since a sub-rectangle of a sub-rectangle is actually a sub-rectangle of the initial one. Why stop at one? All the Optic s that we discussed so far focus on a single sub-rectangle. But, if we want, we can consider also Optic s which focus on multiple sub-rectangles at the same time. We will denote by Traversal A B the Optic s which focus on multiple sub-rectangles of type B inside a main rectangle of type A . For Traversal s we can still define set :: B -> A -> A which replaces all the selected sub-rectangles of type B , inside the main rectangle of type A , with the same vale b of type B , to produce a new A . Similarly, we can define over :: (B -> B) -> A -> A which applies a function to all the selected sub-rectangles of type B , inside the main rectangle of type A , to produce a new A . Another relevant function which makes sense to consider for Traversal s is toListOf :: A -> [B] , which extracts all the values of the selected sub-rectangles of type B from the main rectangle of type A . As usual, we can notice that Traversal A B form a subcategory of Optic A B , since a selection of sub-rectangles inside a selection 0f sub-rectangles is still a selection of sub-rectangles of the main rectangle. Conclusion The graphical representation we just introduced in this post provides us with a tool to navigate various kinds of Optic s and their operations. I hope it can provide a concrete way to understand the basic ideas behind Lens es, Prism s and other Optic s and make it easier to use them. Such a representation could also help to explore and shed some light on the mysterious world of Optic s. One could try to search for other sub-categories in a graphical fashion and then ask what do they correspond to in other Optic representation. For example, what is the sub-category of Optics made by multiple horizontal slices? Or the one made by multiple vertical slices? I need also to mention that such a representation is not able, as far as I can see, to fully represent the whole universe of Optic s. For example, it’s hard to distinguish a Traversal from a Fold , or describe what Grate s are. All in all, I’m confident that describing and explaining optics in this graphical fashion could help people understand their beauty and usefulness! Thanks for reading up to here!