| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Optics
Contents
Description
Introduction...
TODO: motivation behind optics
Synopsis
- module Optics.Optic
- module Optics.AffineFold
- module Optics.AffineTraversal
- module Optics.Equality
- module Optics.Fold
- module Optics.Getter
- module Optics.Iso
- module Optics.IxFold
- module Optics.IxSetter
- module Optics.IxTraversal
- module Optics.Lens
- module Optics.LensyReview
- module Optics.Prism
- module Optics.PrismaticGetter
- module Optics.Review
- module Optics.Setter
- module Optics.Traversal
- module Optics.Re
- module Optics.Indexed
- module Optics.Each
- module Data.Either.Optics
- module Data.Maybe.Optics
- module Data.Tuple.Optics
Basic usage
Differences from lens
This section is work-in-progress
From Adam's talk:
See Talk.pdf, or watch https://skillsmatter.com/skillscasts/10692-through-a-glass-abstractly-lenses-and-the-power-of-abstraction
opticshas an abstract interface:Opticis an opaque type- Cannot write
opticswithout depending on the package, thereforeoptics-coredoesnt' have non GHC-boot library dependencies. (one cannot write prisms withlenswithout depending onprofunctors, indexed optics require depending onlens...) abstract interface:
opticshas better error messages (note:silicais a hybrid approach)>>>set (to fst)... ...A_Getter cannot be used as A_Setter ...abstract interface: better type-inference (optics kind is preserved)
>>>:t traversed % to nottraversed % to not :: Traversable t => Optic A_Fold '[] (t Bool) (t Bool) Bool Boolabstract interface: not all optics have
Join>>>sets map % to not... ...A_Setter cannot be composed with A_Getter ...Opticis aRank1Type(not really before #41), so there are no need forALensetc.- Types that say what they mean
- More comprehensible type errors
- Less vulnerable to the monomorphism restriction
- Free choice of lens implementation
- Indexed optics have different interface.
Drawbacks
- Can’t insert points into the subtyping order post hoc
Technical differences
- Composition operator is
% viewis smart- None of operators is exported from main module
- All ordinary optics are index-preserving by default
- Indexed optics interface is different (let's expand in own section, when the implementation is stabilised)
- There are no
Traversal1 - There is
AffineTraversal - We can't use
traverseas an optic directly, but there is aTraversalcalledtraversed. viewis compatible withlens, but it uses a type class which chooses betweenview1,view01andviewN(See discussion in GitHub #57: Do we needviewat all, and what^.should be)- There are no
from, onlyre(Should there be afromrestricted toIsoor an alias tore? https://github.com/well-typed/optics/pull/43#discussion_r247121380)
Core definitions
Optics.Optic module provides core definitions:
- Opaque
Optictype, - which is parameterised over a type representing an optic flavour;
IsandJoinrelations, illustrated in the graph below;- and optic composition operators
%and%%.

The arrows represent Is relation (partial order). The hierachy is a Join semilattice, for example the
Join of a Lens and a Prism is an AffineTraversal.
>>>:kind! Join A_Lens A_PrismJoin A_Lens A_Prism :: Optics.Internal.Optic.Types.OpticKind_ -> * = An_AffineTraversal
There are also indexed variants of Traversal, Fold and Setter.
Indexed optics are explained in more detail in Differences from lens section.
module Optics.Optic
Optic variants
There are 16 (TODO: add modules for LensyReview and PrismaticGetter) different kinds of optics, each documented in a separate module. Each optic module documentation has formation, introduction, elimination, and well-formedness sections.
The formation sections contain type definitions. For example
-- Tag for a lens. type
A_Lens= 'A_Lens -- Type synonym for a type-modifying lens. typeLenss t a b =OpticA_Lensi i s t a bIn the introduction sections are described the ways to construct the particular optic. Continuing with a
Lensexample:-- Build a lens from a getter and a setter.
lens:: (s -> a) -> (s -> b -> t) ::Lensi s t a bIn the elimination sections are shown how you can destruct the optic into a pieces it was constructed from.
--
Lensis aSetterand aGetter, therefore you canview1::Lensi s t a b -> s -> aset::Lensi s t a b -> b -> s -> tover::Lensi s t a b -> (a -> b) -> s -> tComputation rules tie introduction and elimination combinators together. These rules are automatically fulfilled.
view1(lensf g) s = f sset(lensf g) a s = g s aAll optics provided by the library are well-formed. Constructing of ill-formed optics is possible, but should be avoided. Ill-formed optic might behave differently from what computation rules specify.
A
Lensshould obey three laws, known as GetPut, PutGet and PutPut. See Optics.Lens module for their definitions.
Note: you should also consult the optics hierarchy diagram.
Neither introduction or elimination sections list all ways to construct or use
particular optic kind.
For example you can construct Lens from Iso using sub.
Also, as a Lens is also a Traversal, a Fold etc, so you can use traverseOf, preview
and many other combinators.
module Optics.AffineFold
module Optics.AffineTraversal
module Optics.Equality
module Optics.Fold
module Optics.Getter
module Optics.Iso
module Optics.IxFold
module Optics.IxSetter
module Optics.IxTraversal
module Optics.Lens
module Optics.LensyReview
module Optics.Prism
module Optics.PrismaticGetter
module Optics.Review
module Optics.Setter
module Optics.Traversal
Optics utilities
Re
Some optics can be reversed with re:
into Iso i s t a b,
Iso i b a t s into Getter s t a b etc.
Red arrows illustrate how Review i b a t sre transforms optics:

re is mainly useful to invert Isos:
>>>let _Identity = iso runIdentity Identity>>>view1 (_1 % re _Identity) ('x', "yz")Identity 'x'
Yet we can use a Lens as a Review too:
>>>review (re _1) ('x', "yz")'x'
Note: there are no from combinator.
module Optics.Re
Indexed optics
optics library also provides indexed optics, which provide
an additional index value in mappings:
over::Setters t a b -> (a -> b) -> s -> tiover::IxSetteri s t a b -> (i -> a -> b) -> s -> t
Note that there aren't any laws about indices. Especially in compositions same index may occur multiple times.
The machinery builds on indexed variants of Functor, Foldable, and Traversable classes:
FunctorWithIndex, FoldableWithIndex and TraversableWithIndex respectively.
There are instances for types in the boot libraries.
class (FoldableWithIndexi t,Traversablet) =>TraversableWithIndexi t | t -> i whereitraverse::Applicativef => (i -> a -> f b) -> t a -> f (t b)
Indexed optics can be used as regular ones, i.e. indexed optics gracefully downgrade to regular ones.
>>>toListOf ifolded "foo""foo"
But there is also a combinator to explicitly erase indices:
>>>:t ifoldedifolded :: FoldableWithIndex i f => IxFold i (f a) a
>>>:t unIx ifoldedunIx ifolded :: FoldableWithIndex i f => Optic A_Fold '[] (f b) (f b) b b
As the example above illustrates (TODO: will do),
regular and indexed optics have the same kind, in this case .
Regular optics simply don't have any indices.
The provided type aliases Optic A_FoldIxFold, IxSetter and IxTraversal
are variants with a single index.
In the diagram below, the optics hierachy is amended with these (singly) indexed variants (in blue).
Orange arrows mean
"can be used as one, assuming it's composed with any optic below the
orange arrow first". For example. _1 is not an indexed fold, but
is, because it's an indexed traversal, so it's
also an indexed fold.itraversed % _1
>>>let fst' = _1 :: Lens (a, c) (b, c) a b>>>:t fst' % itraversedfst' % itraversed :: TraversableWithIndex i t => Optic A_Traversal '[i] (t a, c) (t b, c) a b

TODO: write about icompose and multiple indices.
There are yet no IxAffineFold, IxAffineTraversal etc, but they can be added.
module Optics.Indexed
Each
A Traversal for a (potentially monomorphic) container.
>>>over each (*10) (1,2,3)(10,20,30)
module Optics.Each
Optics for concrete base types
module Data.Either.Optics
module Data.Maybe.Optics
module Data.Tuple.Optics