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
||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|
(^@..) :: 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 (
>>> 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
||Set the focus|
||Modify the focus|
||Denotes a Setter/Modifier|
||Denotes a Setter/Modifier over a
||Include the altered focus with the result|
||Include the unaltered focus with the result|
||Perform a traversal over the focus|
||Add to the focus|
||Subtract from the focus|
||Multiply the focus|
||Divide the focus|
||Pass the index to the modification function|
(<>~) :: Monoid a => Traversal' s a -> a -> s -> s
: A setter (
<>) 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
=), 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
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.
For example, to determine which type we get by composing
_Just we first check each of their types to discover that
traverse is a
_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
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.
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)
Reviews generalize over the Profunctor type and thus can also have constraints on
p a (f b) -> p s (f t)
Keep this in mind when reading the chart.
|Fold||Contravariant f, Applicative 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:
|fold queries: (toListOf, sumOf, lengthOf, etc.)||Const|
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.