Skip to content

Instantly share code, notes, and snippets.

@ChrisPenner
Last active March 20, 2024 05:38
Show Gist options
  • Star 109 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ChrisPenner/1f7b6923448b3396a45d04a2b6b9d066 to your computer and use it in GitHub Desktop.
Save ChrisPenner/1f7b6923448b3396a45d04a2b6b9d066 to your computer and use it in GitHub Desktop.
Optics Cheatsheet

Optics By Example Cheat Sheets

The following are appendices from Optics By Example, a comprehensive guide to optics from beginner to advanced! If you like the content below, there's plenty more where that came from; pick up the book!

Operator Cheat Sheet

Operators may look like an earthquake hit the mechanical keyboard factory, but there's actually a bit of a language to the whole thing which starts to make sense after a bit of practice.

Once you get used to the ideas you can usually guess the name of a symbol which does what you need, and it'll usually exist!

Legend for Getters

Symbol Description
^ Denotes a Getter
@ Include the index with the result
. Get a single value
.. Get a List of values
? Maybe get the first value
! Force a result or throw an exception if missing

Examples

(^@..) :: s -> IndexedFold i s a -> [(i, a)] : A getter (^) which includes the index (@) in a list of all focuses (..).

"Yarrr" ^@.. folded
[(0,'Y'),(1,'a'),(2,'r'),(3,'r'),(4,'r')]

(^?!) :: s -> Traversal' s a -> a : A getter (^) which forcibly gets (!) a possibly missing (?) value.

>>> Just "Nemo" ^?! _Just
"Nemo"
>>> Nothing ^?! _Just
*** Exception: (^?!): empty Fold
CallStack (from HasCallStack):
  error, called at src/Control/Lens/Fold.hs:1285:28 in lens
E:Control.Lens.Fold
  ^?!, called at <interactive>:1:1 in interactive:Ghci4

Legend for Setters/Modifiers

Symbol Description
. Set the focus
% Modify the focus
~ Denotes a Setter/Modifier
= Denotes a Setter/Modifier over a MonadState context
< Include the altered focus with the result
<< Include the unaltered focus with the result
%% Perform a traversal over the focus
<> mappend over the focus
? Wrap in Just before setting
+ Add to the focus
- Subtract from the focus
* Multiply the focus
// Divide the focus
|| Logically or the focus
&& Logically and the focus
@ Pass the index to the modification function

Examples

(<>~) :: Monoid a => Traversal' s a -> a -> s -> s : A setter (~) which mappends (<>) a new value to the focus.

>>> ["Polly want a", "Salty as a soup"] & traverse <>~ " cracker!"
["Polly want a cracker!","Salty as a soup cracker!"]

(<<%@=) :: MonadState s m => Traversal s s a b -> (i -> a -> b) -> m a : Modify (%) the focus from within a MonadState (=), passing the index (@) to the function as well. Also return unaltered (<<) original value.

This one's a bit tricky:

>>> runState (itraversed <<%@= \k v -> "item: " <> k <> ", quantity: " <> v) (M.singleton "bananas" "32")
("32",fromList [("bananas","item: bananas, quantity: 32")])

(%%~) :: Traversal s t a b -> (a -> f b) -> s -> f t : A setter (~) which traverses (%%) a function over the focus.

>>> (1, 2) & both %%~ (\n -> if even n then Just n else Nothing)
Nothing

>>> (2, 4) & both %%~ (\n -> if even n then Just n else Nothing)
Just (2,4)

Optic Composition Table

This table is adapted from the documentation of the Scala optics library Monocle. I've simply altered it to match the lens library.

The value of each cell denotes the most general type you can achieve by composing the column header with the row header.

The type of an optic is determined by collecting all the constraints of all composed optics in a path. Since constraints collection acts as a set union (which is commutative) the order of composition has no effect on the resulting optic type. Therefore the following table is symmetric across its diagonal.

"--" signifies that the optics are incompatible and do not compose.

Fold Getter Setter Traversal Prism Lens Iso
Fold Fold Fold -- Fold Fold Fold Fold
Getter Fold Getter -- Fold Fold Getter Getter
Setter -- -- Setter Setter Setter Setter Setter
Traversal Fold Fold Setter Traversal Traversal Traversal Traversal
Prism Fold Fold Setter Traversal Prism Traversal Prism
Lens Fold Getter Setter Traversal Traversal Lens Lens
Iso Fold Getter Setter Traversal Prism Lens Iso

For example, to determine which type we get by composing traverse with _Just we first check each of their types to discover that traverse is a Traversal and _Just is a Prism. We then look up the column (or row) with the header Traversal, then find the cell with the corresponding header Prism on the other axis. Performing this look up we see that the composition traversed . _Just results in a Traversal.

Optic Compatibility Chart

The following chart details which optics are valid substitutions for one another.

As an example, let's say we were curious if all Prisms are a valid Traversal; we first find the row with Prism in the first column; then find the corresponding Traversal column and find a Yes; meaning that a Prism is a valid substitution for a Traversal.

The optic in the first column is the optic you have, the other column headers represent the type you'd like to use it as.

Fold Getter Setter Traversal Lens Review Prism Iso
Fold
Getter
Setter
Traversal
Lens
Review
Prism
Iso

Optic Constraints and Types

When reading type-errors or strange optics signatures you can usually guess the type of optic an operation needs by matching the constraints against the following chart.

Remember that most optics take the form:

(a -> f b) -> (s -> f t)

However Isos, Prisms and Reviews generalize over the Profunctor type and thus can also have constraints on p:

p a (f b) -> p s (f t)

Keep this in mind when reading the chart.

Optic Constraints
Lens Functor f
Fold Contravariant f, Applicative f
Traversal Applicative f
Setter Settable f
Getter Contravariant f, Functor f
Iso Functor f, Profunctor p
Prism Applicative f, Choice p
Review Settable f, Profunctor p, Bifunctor p

Composing optics adds constraints together, then running an optic with an action matches a data type which fulfills those constraints. Here's a table of lens actions and the data-type they use to "run" the optics you pass to them:

Action Data type
view (^.) Const
set (.~) Identity
over (%~) Identity
fold queries: (toListOf, sumOf, lengthOf, etc.) Const
review (#) Tagged
traverseOf (%%~) None
matching Market

Thanks for reading! If you learned something, consider getting the rest of the book, this cheat sheet only scratches the surface!

You can get Optics By Example here. Thanks!

Feel free to share this cheat-sheet with your friends.

@ekmett
Copy link

ekmett commented Jan 2, 2020

A Traversal cannot be used as a Getter, otherwise your matrix version of my feature join semilattice looks right.

@ChrisPenner
Copy link
Author

Yup! That was just a typo, all fixed up 👍

@3v0k4
Copy link

3v0k4 commented Dec 22, 2020

Love it. Thanks Chris!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment