Read on for a general introduction to the notion of optics, or if you are
familiar with them already, you may wish to jump ahead to the "What is the
abstract interface?" section below in Optics#abstract.
What are optics?
An optic is a first-class, composable notion of substructure. As a highly
abstract concept, the idea can be approached by considering several examples
of optics and understanding their common features. What are the possible
relationships between some "outer" type S and some "inner" type A?
(For simplicity we will initially ignore the possibility of type-changing
update operations, which change A to some other type B and hence change
S to some other type T. These are fully supported by the library, at the
cost of some extra type parameters.)
First, S and A may be isomorphic, i.e. there exist mutually inverse
functions to convert S -> A and A -> S. This is a somewhat trivial
notion of substructure: A is just another way to represent "all of S".
An Iso' S A is an isomorphism between S and A, with the conversion
functions given by view and review. For example, given
newtype Age = Age Int
there is an isomorphism between the newtype and its representation:
If S is a simple product type (i.e. it has a single constructor with one or
more fields), A may be a single field of S. More generally, A may be
"part of S" in the sense that S is isomorphic to the pair (A,C) for
some type C representing the other fields. In this case, there is a
projection function S -> A for getting the value of the field, but the
update function (setting the value of the field) requires the "rest of S"
and so has type A -> S -> S.
A Lens' S A captures the structure of A being a field of S, with the
projection function given by view and the update function by set. For
example, for the pair type (X,Y) there are lenses for each component:
(Note that the update function could arguably have the more precise type A
-> C -> S, since we do not expect the result of setting a field to depend on
the previous value of the field. However, making C explicit turns out to
be awkward, so instead we impose laws to require that the result of setting
the field depends only on C, and, more generally, that the lens behaves as
we would expect.)
If S is a simple sum type (i.e. it has one or more constructors, each with
a single field), A may be the type of the field for a single constructor of
S. More generally, S may be isomorphic to the disjoint union Either D
A for some type D representing the other constructors. In this case,
projecting out A from S (pattern-matching on the constructor) may fail,
so it has type S -> Maybe A. In the reverse direction we have a function
of type A -> S representing the constructor itself.
A Prism' S A captures the structure of A being a constructor of S,
with the partial projection function given by preview and the constructor
function given by review. For example, for the type Either X Y there
is a prism for each constructor:
Alternatively, S may "contain" the substructure A a variable number of
times. In this case, the projection function extracts the (possibly zero or
many) elements so has type S -> [A], while the update function may take
different values for different elements so has type (A -> A) -> S -> S
(though in fact more general formulations are possible).
A Traversal' S A captures the structure of A being contained in S
perhaps multiple times, with the list of values given by toListOf and the
update function given by over . For example, for the type Maybe X there
is a traversal that may return zero or one element:
(In fact, traversals of at most one element are known as affine traversals,
see Optics.AffineTraversal.)
In general
So far we have seen four different kinds of optic or "notions of
substructure", and many more are possible. Observe the important properties
they have in common:
There are subtyping relationships between different optic kinds. Any
isomorphism is trivially a lens and a prism (with no other fields or
constructors, respectively). Any lens is a traversal (where the list of
elements is always a singleton list), and any prism is also a traversal
(where there will be zero or one element depending on whether the
constructor matches). This was implicit in the fact that we
used the same operators in multiple cases: view gives the projection
function of both an isomorphism and a lens, but cannot be applied to a
traversal.
Optics can be composed. If S is isomorphic to U and U is isomorphic
to A then S is isomorphic to A, and similarly for other optic kinds.
Composition and subtyping interact: a lens and a prism can be composed, by
first thinking of them as traversals using the subtyping relationship. That
is, if S has a field U, and U has a constructor A, then S
contains zero or one As that we can pick out with a traversal (but in
general there is neither a lens from S to A nor a prism).
Each optic kind can be described by certain operations it enables. For
example lenses support projection and update, while prisms support partial
projection and construction.
Optics are subject to laws, which are necessary for the operations to make
sense.
The point of the optics library is to capture this common pattern.
What is the abstract interface?
A key principle behind this library is the belief that optics are useful as
an abstract concept, and that the purpose of types is to capture abstract
concepts and make them useful. The programmer using optics should be able to
think in terms of the abstract interface, rather than the details of the
implementation, and implementation choices should (as far as possible) not
dictate the interface.
Each optic kind is identified by a "tag type" (such as A_Lens), which is an
empty data type. The type of the actual optics (such as Lens) is obtained
by applying the Optic newtype wrapper to the tag type.
NoIx as the second parameter to Optic indicates that the optic is not
indexed. See the "Indexed optics" section below in Optics#indexed for
further discussion of indexed optics.
The details of the internal implementation of Optic are hidden behind an
abstraction boundary, so that the library can be used without needing to
think about the particular implementation choices.
Specification of optics interfaces
Each different kind of optic is documented in a separate module describing
its abstract interface, in a standard format with at least formation,
introduction, elimination, and well-formedness sections. See "Optic
kinds" below in Optics#optickinds for a list of these modules.
The formation sections contain type definitions. For example
Optics.Lens defines:
-- Type synonym for a type-modifying lens.
type Lens s t a b = OpticA_LensNoIx s t a b
The introduction sections describe the canonical way to construct each
particular optic. Continuing with a Lens example:
-- Build a lens from a getter and a setter.
lens :: (s -> a) -> (s -> b -> t) :: Lens s t a b
Correspondingly, the elimination sections show how you can destruct the
optic into the pieces from which it was constructed.
-- A Lens is a Setter and a Getter, therefore you can specialise types to obtain
view :: Lens s t a b -> s -> a
set :: Lens s t a b -> b -> s -> t
The computation rules tie introduction and elimination forms
together. These rules are automatically fulfilled by the library (for
well-formed optics).
The well-formedness sections describe the laws that each optic should
obey. As far as possible, all optics provided by the library are
well-formed, but in some cases this depends on invariants that cannot be
expressed in types. Ill-formed optics might behave differently from what
the computation rules specify.
For example, a Lens should obey three laws, known as GetPut, PutGet
and PutPut. See the Optics.Lens module for their definitions. The
user of the lens introduction form must ensure that these laws are
satisfied.
Some optic kinds have additional introduction forms,
additional elimination forms or combinators sections, which give
alternative ways to create and use optics of that kind. In principle these
are expressible in terms of the canonical introduction and elimination
rules.
The subtyping section gives the "tag type" (such as A_Lens), which in
particular is accompanied by Is instances that define the subtyping
relationship discussed in the following section.
Subtyping
There is a subtyping relationship between optics, implemented using
typeclasses. The Is typeclass captures the property that one optic kind
can be used as another, and the castOptic function can be used to
explicitly cast between optic kinds. Is forms a partial order, represented
in the graph below. For example, a lens can be used as a traversal, so there
are arrows from Lens to Traversal (via AffineTraversal) and there is an
instance of IsA_LensA_Traversal.
Introduction forms (constructors) return a concrete optic kind, while
elimination forms (destructors) are generally polymorphic in the optic kind
they accept. This means that it is not normally necessary to explicitly cast
between optic kinds. For example, we have
so view can be used with isomorphisms or lenses, as these can be converted
to a Getter.
If an explicit cast is needed, you can use castOptic. This arises when you
use optics of different kinds in a context that requires them to have the
same type. For example [folded, traversed] gives a type error (since
A_Traversal is not A_Fold) but [folded, castOptictraversed]
works.
The optic kind module (e.g. Optics.Lens) does not list all ways to
construct or use particular the optic kind. For example, since a Lens is
also a Traversal, a Fold etc, so you can use traverseOf, preview and
many other combinators with lenses.
Subtype hierarchy
This graph gives an overview of the optic kinds and their subtype
relationships:
In addition to the optic kinds included in the diagram, there are also
indexed variants such as IxLens, IxGetter, IxAffineTraversal,
IxTraversal, IxAffineFold, IxFold and IxSetter. These are explained
in more detail in the "Indexed optics" section below in Optics#indexed.
Composition
Since optics are not functions, they cannot be composed with the (.)
operator. Instead there is a separate composition operator (%). The
composition operator returns the common supertype of its arguments, or
generates a type error if the composition does not make sense.
The optic kind resulting from a composition is the least upper bound (join)
of the optic kinds being composed, if it exists. The JoinKinds class
computes the least upper bound given two optic kind tags. For example the
constraint 'JoinKinds A_Lens A_Prism k' makes GHC infer that k must be
An_AffineTraversal.
The join does not exist for some pairs of optic kinds, which means that they
cannot be composed. For example there is no optic kind above both Setter
and Fold:
Example1 expression
>>> :t mapped % folded......A_Setter cannot be composed with A_Fold...
The (.) operator from Control.Category cannot be used to
compose optics either, because it would not support type-changing optics or
composing optics of different kinds.
Comparison with lens
The lens package is the best known Haskell library for optics, and
established many of the foundations on which the optics package builds (not
least in quite a bit of code having been directly ported). It defines optics
based on the van Laarhoven representation, where each optic kind is
introduced as a transparent type synonym for a complex polymorphic type,
for example:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
In contrast, optics tries to preserve an abstraction boundary between the
interface of optics and their implementation. Optic kinds are expressed
directly in the types, as Optic is an opaque newtype:
The choice of representation of Optic is then an implementation detail, not
essential for understanding the library. (In fact, optics uses the
profunctor representation rather than the van Laarhoven representation;
this affects the optic kinds and operations that can be conveniently
supported, but not the essence of the design.)
Our design choice to use opaque rather than transparent abstractions
leads to various consequences, both positive and negative, which are explored
in the following subsections.
Advantages of the opaque design
Since the interface is deliberately chosen rather than to some extent
determined by the implementation, we are free to choose a more restricted
interface where doing so leads to conceptual simplicity. For example, in
lens, the view function can be used with a Fold provided the result
type has a Monoid instance, and the multiple targets of the Fold will be
combined monoidally. This behaviour can be confusing, so in optics a
Fold cannot be silently used as a Getter, and we prefer to have view
work on Getters and define a separate foldOf operator for use on
Folds. (But the gview function is available for users who may prefer
otherwise.)
In general, opaque abstractions lead to better results from type inference
(the optic kind is preserved in the inferred type):
Example1 expression
>>> :t traversed % to nottraversed % to not :: Traversable t => Optic A_Fold '[] (t Bool) (t Bool) Bool Bool
Error messages are domain-specific:
Example1 expression
>>> set (to fst)......A_Getter cannot be used as A_Setter...
Composing incompatible optics yields a sensible error:
Example1 expression
>>> sets map % to not......A_Setter cannot be composed with A_Getter...
Since Optic is a rank-1 type, it is easy to store optics in a
datastructure:
Example1 expression
>>> :t [folded, backwards_ folded][folded, backwards_ folded] :: Foldable f => [Fold (f a) a]
It is possible to define aliases for optics without the monomorphism
restriction spoiling the fun:
Example1 expression
>>> let { myoptic = _1; p = ('x','y') } in (view myoptic p, set myoptic 'c' p)('x',('c','y'))
Finally, having an abstract interface gives more freedom of choice in the
internal implementation. If there is a compelling reason to switch to an
alternative representation, one can in principle do so without changing the
interface.
Disadvantages of the opaque design
Since Optic is a newtype, other libraries that wish to define optics must
depend upon its definition. In contrast, with a transparent representation,
and since the van Laarhoven representations of lenses and traversals depend
only on definitions from base, it is possible for libraries to define them
without any extra library dependencies (although this does not hold for more
advanced optic kinds such as prisms or indexed optics). To address this, the
present library is split into a package optics-core, which has a minimal
dependency footprint intended for use in libraries, and the
"batteries-included" optics package for use in applications.
It is something of an amazing fact that the composition operator for
transparent optics is just function composition. Moreover, since Haskell
uses (.) for function composition, lens is able to support a pseudo-OOP
syntax. In contrast, optics must use a different composition operator
(%). Optic does not quite form a Category, thanks to
type-changing optics.
Rather than emerging naturally from the definitions, opportunities for
polymorphism have to be identified in advance and explicitly introduced using
type classes. Similarly, the set of optic kinds and the subtyping
relationships between them must be fixed in advance, and cannot be added to
in downstream libraries. Thus in a sense the opaque approach is more
restrictive than the transparent one. There are cases in lens where the
types work out nicely and permit abstraction-breaking-but-convenient
shortcuts, such as applying a Traversal as a traverse-like function,
whereas optics requires a call to traverseOf.
More specific differences
The sections above set out the major conceptual differences from the lens
package, and their advantages and disadvantages. Some more specific design
differences, which may be useful for comparison or porting code between the
libraries. This list is no doubt incomplete.
The composition operator is (%) rather than (.) and is defined as
infixl 9 instead of infixr 9.
Fewer operators are provided, and some of them are not exported from the
main Optics module. Import Optics.State.Operators if you want them.
The view function and corresponding (^.) operator work
only for Getters and have a more restricted type. The equivalent for
Folds is foldOf, and you can use preview for
AffineFolds. Alternatively you can use gview which is more compatible
with view from lens, but it uses a type class to choose between view,
preview and foldOf.
Indexed optics are rather different, as described in the "Indexed optics"
section below in Optics#indexed. All ordinary optics are
"index-preserving", so there is no separate notion of an index-preserving
optic.
Functions ifiltered and indices are defined as optic combinators due to
restrictions of internal representation.
We can't use traverse as an optic directly. Instead there is a
Traversal called traversed. Similarly traverseOf must be used to
apply a Traversal, rather than simply using it as a function.
The re combinator produces a different optic kind depending on the kind
of the input Iso, for example Review reverses to Getter while a
reversed Iso is still an Iso. Thus there is no separate from
combinator for reversing Isos.
singular (isingular for indexed optics) doesn't produce a partial lens
that might fail with a runtime error, but an affine traversal.
<> cannot be used to combine Folds, so summing should be used instead
(see the "Monoid structures" section below in Optics#monoids).
Other resources
Talks
(2020-10) User Friendly Optics - a talk about the optics library in comparison to the lens library by Andrzej Rybczak
(2018-10) Profunctors and Data Accessors - a talk on basics of profunctors and how they relate to data accessors such as lenses, prisms and traversals by Andrzej Rybczak
(2020-01) Case study: migrating from lens to optics - a blog post by Oleg Grenrus, potentially useful if you wish to migrate an existing codebase to optics from lens
If you are writing a library for which it is important to keep the dependency
footprint minimal, you may wish to depend upon optics-core instead (and
perhaps optics-extra or optics-th), and then:
The first parameter k identifies the particular optic kind (e.g. A_Lens
or A_Traversal).
The parameter is is a list of types available as indices. This will
typically be NoIx for unindexed optics, or WithIx for optics with a
single index. See the "Indexed optics" section of the overview documentation
in the Optics module of the main optics package for more details.
The parameters s and t represent the "big" structure,
whereas a and b represent the "small" structure.
Generate sensible error messages in case a user tries to pass either an
unindexed optic or indexed optic with unflattened indices where indexed optic
with a single index is expected.
Instances7HasSingleIndex, …
(TypeError ('Text"Indexed optic is expected"), '[] ~ '[i]) => HasSingleIndex '[] iDefined in optics-core-0.4.1.1 · Optics.Internal.Indexed
HasSingleIndex '[i] iDefined in optics-core-0.4.1.1 · Optics.Internal.Indexed
(TypeError ('Text"Use (<%>) or icompose to combine indices of type " ':<>:ShowTypesis), is~ '[i1, i2], is~ '[i]) => HasSingleIndex '[i1, i2] iDefined in optics-core-0.4.1.1 · Optics.Internal.Indexed
(TypeError ('Text"Use icompose3 to combine indices of type " ':<>:ShowTypesis), is~ '[i1, i2, i3], is~ '[i]) => HasSingleIndex '[i1, i2, i3] iDefined in optics-core-0.4.1.1 · Optics.Internal.Indexed
(TypeError ('Text"Use icompose4 to combine indices of type " ':<>:ShowTypesis), is~ '[i1, i2, i3, i4], is~ '[i]) => HasSingleIndex '[i1, i2, i3, i4] iDefined in optics-core-0.4.1.1 · Optics.Internal.Indexed
(TypeError ('Text"Use icompose5 to flatten indices of type " ':<>:ShowTypesis), is~ '[i1, i2, i3, i4, i5], is~ '[i]) => HasSingleIndex '[i1, i2, i3, i4, i5] iDefined in optics-core-0.4.1.1 · Optics.Internal.Indexed
(TypeError ('Text"Use icomposeN to flatten indices of type " ':<>:ShowTypesis), is~ (i1 ': i2 ': i3 ': i4 ': i5 ': i6 ': is'), is~ '[i]) => HasSingleIndex (i1 ': i2 ': i3 ': i4 ': i5 ': i6 ': is') iDefined in optics-core-0.4.1.1 · Optics.Internal.Indexed
Normally you can simply use (%) instead, but this may be useful to help
type inference if the type of one of the optics is otherwise
under-constrained.
Computes the least upper bound of two optics kinds.
In presence of a JoinKinds k l m constraint Optic m represents the least
upper bound of an Optic k and an Optic l. This means in particular that
composition of an Optic k and an Optic k will yield an Optic m.
& is a reverse application operator. This provides notational
convenience. Its precedence is one higher than that of the forward
application operator $, which allows & to be nested in $.
This is a version of flipid, where id is specialized from a -> a to (a -> b) -> (a -> b)
which by the associativity of (->) is (a -> b) -> a -> b.
flipping this yields a -> (a -> b) -> b which is the type signature of &
Examples
Example1 expression
>>> 5 & (+1) & show"6"
Example1 expression
>>> sqrt $ [1 / n^2 | n <- [1..1000]] & sum & (*6)3.1406380562059946
>>> over (_1 % _Just `adjoin` _2 % _Right) not (Just True, Right False)(Just False,Right True)
Note: if the argument traversals are not disjoint, the result will not
respect the Traversal laws, because it will visit the same element multiple
times. See section 7 of
Understanding Idiomatic Traversals Backwards and Forwards
by Bird et al. for why this is illegal.
TODO DOC: what exactly is the strictness property?
Example:
f :: Int -> (Int, a) -> (Int, a)
f k acc
| k > 0 = f (k - 1) $ over'_1 (+1) acc
| otherwise = acc
runs in constant space, but would result in a space leak if used with over.
Note that replacing $ with $! or _1 with
_1' (which amount to the same thing) doesn't help when
over is used, because the first coordinate of a pair is never forced.
Rewrite by applying a rule everywhere you can. Ensures that the rule cannot
be applied anywhere in the result:
propRewriteOf l r x = all (Data.Just.isNothing. r) (universeOf l (rewriteOf l r x))
Usually transformOf is more appropriate, but rewriteOf can give better
compositionality. Given two single transformations f and g, you can
construct \a -> f a <|> g a which performs both rewrites until a fixed
point.
This Prism compares for approximate equality with a given value and a
predicate for testing, an example where the value is the empty list and the
predicate checks that a list is empty (same as _Empty with the
AsEmpty list instance):
The ifindMOf function takes an IxFold, a monadic predicate that is also
supplied the index, a structure and returns in the monad the left-most
element of the structure matching the predicate, or Nothing if there is no
such element.
When you don't need access to the index then findMOf is more flexible in
what it accepts.
The ifindOf function takes an IxFold, a predicate that is also supplied
the index, a structure and returns the left-most element of the structure
along with its index matching the predicate, or Nothing if there is no such
element.
When you don't need access to the index then findOf is more flexible in
what it accepts.
The findMOf function takes a Fold, a monadic predicate and a structure
and returns in the monad the leftmost element of the structure matching the
predicate, or Nothing if there is no such element.
Example1 expression
>>> findMOf each (\x -> print ("Checking " ++ show x) >> return (even x)) (1,3,4,6)"Checking 1""Checking 3""Checking 4"Just 4
Example1 expression
>>> findMOf each (\x -> print ("Checking " ++ show x) >> return (even x)) (1,3,5,7)"Checking 1""Checking 3""Checking 5""Checking 7"Nothing
findMOffolded :: (Monad m, Foldable f) => (a -> m Bool) -> f a -> m (Maybe a)
The findOf function takes a Fold, a predicate and a structure and
returns the leftmost element of the structure matching the predicate, or
Nothing if there is no such element.
Obtain a potentially empty IxAffineTraversal by taking the element from
another AffineFold and using it as an index.
- Note: This is not a legal IxTraversal, unless you
are very careful not to invalidate the predicate on the target (see
unsafeFiltered for more details).
Type synonym for a type-modifying van Laarhoven indexed affine traversal.
Note: this isn't exactly van Laarhoven representation as there is no
Pointed class (which would be a superclass of Applicative that contains
pure but not <*>). You can interpret the first argument as a dictionary
of Pointed that supplies the point function (i.e. the implementation of
pure).
The lookupOf function takes a Fold, a key, and a structure containing
key/value pairs. It returns the first value corresponding to the given
key. This function generalizes lookup to work on an arbitrary Fold
instead of lists.
In the interest of efficiency, This operation has semantics more strict than
strictly necessary. \o -> getMax . foldMapOf o Max has lazier
semantics but could leak memory.
In the interest of efficiency, This operation has semantics more strict than
strictly necessary. \o -> getMin . foldMapOf o Min has lazier
semantics but could leak memory.
Type synonym for a type-modifying van Laarhoven affine traversal.
Note: this isn't exactly van Laarhoven representation as there is
no Pointed class (which would be a superclass of Applicative
that contains pure but not <*>). You can interpret the first
argument as a dictionary of Pointed that supplies the point
function (i.e. the implementation of pure).
A TraversalVL has Applicative available and
hence can combine the effects arising from multiple elements using
<*>. In contrast, an AffineTraversalVL has no way to combine
effects from multiple elements, so it must act on at most one
element. (It can act on none at all thanks to the availability of
point.)
>>> Map.fromList [(1,"world")] ^. at 1Just "world"
Example1 expression
>>> at 1 ?~ "hello" $ Map.emptyfromList [(1,"hello")]
Note: Usage of this function might introduce space leaks if you're not
careful to make sure that values put inside the Just constructor are
evaluated. To force the values and avoid such leaks, use at' instead.
Note:Map-like containers form a reasonable instance, but not
Array-like ones, where you cannot satisfy the Lens laws.
Instances7At, …
AtIntSetDefined in optics-core-0.4.1.1 · Optics.At.Core
Ordk => At (Setk)Defined in optics-core-0.4.1.1 · Optics.At.Core
At (IntMapa)Defined in optics-core-0.4.1.1 · Optics.At.Core
At (Maybea)Defined in optics-core-0.4.1.1 · Optics.At.Core
(Eqk, Hashablek) => At (HashSetk)Defined in optics-extra-0.4.2.1 · Optics.At · orphan
Ordk => At (Mapka)Defined in optics-core-0.4.1.1 · Optics.At.Core
(Eqk, Hashablek) => At (HashMapka)Defined in optics-extra-0.4.2.1 · Optics.At · orphan
This class provides a simple Lens that lets you view (and modify)
information about whether or not a container contains a given Index.
Instances are provided for Set-like containers only.
Type family that takes a key-value container type and returns the type of
keys (indices) into the container, for example Index (Map k a) ~ k.
This is shared by Ixed, At and Contains.
Instances32Index, …
typeIndexByteString = IntDefined in optics-extra-0.4.2.1 · Optics.At · orphan
Type family that takes a key-value container type and returns the type of
values stored in the container, for example IxValue (Map k a) ~ a. This
is shared by both Ixed and At.
Type family that takes a key-value container type and returns the kind
of optic to index into it. For most containers, it's An_AffineTraversal,
Representable (Naperian) containers it is A_Lens, and multi-maps would
have A_Traversal.
Type family that takes a key-value container type and returns the kind
of optic to index into it. For most containers, it's An_AffineTraversal,
Representable (Naperian) containers it is A_Lens, and multi-maps would
have A_Traversal.
In the following diagram, red arrows illustrate how re transforms optics.
The ReversedLens and ReversedPrism optic kinds are backwards versions
of Lens and Prism respectively, and are present so that re . re
does not change the optic kind.
Turn read-write optic into its read-only counterpart (or leave read-only
optics as-is).
This is useful when you have an optic :: Optic k is s t a b of read-write
kind k such that s, t, a, b are rigid, there is no evidence that
s ~ t and a ~ b and you want to pass optic to one of the functions
that accept read-only optic kinds.
Example:
Example1 expression
>>> let fstIntToChar = _1 :: Lens (Int, r) (Char, r) Int Char
Example1 expression
>>> :t view fstIntToChar......Couldn't match type ‘Char’ with ‘Int’...
Use the target of a Lens, Iso, or Getter in the current state.
Example1 expression
>>> evalState (use _1) ('a','b')'a'
Example1 expression
>>> evalState (use _2) ("hello","world")"world"
View
A generalized view function gview, which returns a single result (like
view) if the optic is a Getter, a Maybe result (like preview) if
the optic is an AffineFold, or a monoidal summary of results (like
foldOf) if the optic is a Fold. In addition, it works for any
Control.Monad.Reader.MonadReader, not just (->).
Example1 expression
>>> gview _1 ('x','y')'x'
Example1 expression
>>> gview _Left (Left 'x')Just 'x'
Example1 expression
>>> gview folded ["a", "b"]"ab"
Example1 expression
>>> runReaderT (gview _1) ('x','y') :: IO Char'x'
This module is experimental. Using the more type-restricted variants is
encouraged where possible.
This is a generalized form of listen that only extracts the portion of
the log that is focused on by a Getter. If given a Fold or a Traversal
then a monoidal summary of the parts of the log that are visited will be
returned.
This is a generalized form of listen that only extracts the portion of
the log that is focused on by a Getter. If given a Fold or a Traversal
then a monoidal summary of the parts of the log that are visited will be
returned.
A class to zoom in, changing the Control.Monad.State.State supplied
by many different monad transformers, potentially quite deep in a monad
transformer stack.
This class allows us to zoom in, changing the State supplied by many
different monad transformers, potentially quite deep in a monad transformer
stack.
Its functions can be used to run a monadic action in a larger State than it
was defined in, using a Lens', an AffineTraversal' or a Traversal'.
This is commonly used to lift actions in a simpler StateMonad into a
StateMonad with a larger State type.
When used with a Traversal' over multiple values, the actions for each
target are executed sequentially and the results are aggregated.
This can be used to edit pretty much any Monad transformer stack with a
State in it!
Example1 expression
>>> flip L.evalState ('a','b') $ zoom _1 $ use equality'a'
This class allows us to magnify part of the environment, changing the
environment supplied by many different Monad transformers. Unlike zoom
this can change the environment of a deeply nested Monad transformer.
Its functions can be used to run a monadic action in a larger environment
than it was defined in, using a Getter or an AffineFold.
They act like local, but can in many cases
change the type of the environment as well.
They're commonly used to lift actions in a simpler ReaderMonad into a
Monad with a larger environment type.
They can be used to edit pretty much any Monad transformer stack with an
environment in it:
Extends Magnify with an ability to magnify using a Fold over multiple
targets so that actions for each one are executed sequentially and the
results are aggregated.
There is however no sensible instance of MagnifyMany for StateT.
As the example above illustrates, regular and indexed optics have the same
tag in the first parameter of Optic, in this case A_Fold. Regular optics
simply don't have any indices. The provided type aliases IxLens,
IxGetter, IxAffineTraversal, IxAffineFold, IxTraversal, IxFold and
IxSetter are variants with a single index. In general, the second parameter
of the Optic newtype is a type-level list of indices, which will typically
be NoIx (the empty index list) or (WithIx i) (a singleton list).
When two optics are composed with (%), the index lists are concatenated.
Thus composing an unindexed optic with an indexed optic preserves the
indices, or composing two indexed optics retains both indices:
Alternatively, you can use one of the (<%) or (%>) operators to compose
indexed optics and pick the index to retain, or the (<%>) operator to
retain a pair of indices:
In the diagram below, the optics hierarchy 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
itraversed % _1 is, because it's an indexed traversal, so it's
also an indexed fold.
Example2 expressions
>>> let fst' = _1 :: Lens (a, c) (b, c) a b>>> :t fst' % itraversedfst' % itraversed :: TraversableWithIndex i f => Optic A_Traversal '[i] (f a, c) (f b, c) a b
Construct a conjoined indexed optic that provides a separate code path when
used without indices. Useful for defining indexed optics that are as
efficient as their unindexed equivalents when used without indices.
Note:conjoined f g is well-defined if and only if f ≡
noIx g.
The ifindMOf function takes an IxFold, a monadic predicate that is also
supplied the index, a structure and returns in the monad the left-most
element of the structure matching the predicate, or Nothing if there is no
such element.
When you don't need access to the index then findMOf is more flexible in
what it accepts.
The ifindOf function takes an IxFold, a predicate that is also supplied
the index, a structure and returns the left-most element of the structure
along with its index matching the predicate, or Nothing if there is no such
element.
When you don't need access to the index then findOf is more flexible in
what it accepts.
Obtain a potentially empty IxAffineTraversal by taking the element from
another AffineFold and using it as an index.
- Note: This is not a legal IxTraversal, unless you
are very careful not to invalidate the predicate on the target (see
unsafeFiltered for more details).
Type synonym for a type-modifying van Laarhoven indexed affine traversal.
Note: this isn't exactly van Laarhoven representation as there is no
Pointed class (which would be a superclass of Applicative that contains
pure but not <*>). You can interpret the first argument as a dictionary
of Pointed that supplies the point function (i.e. the implementation of
pure).
(s~t, a~b) => IxOpticA_FoldstabDefined in optics-core-0.4.1.1 · Optics.Indexed.Core
(s~t, a~b) => IxOpticA_GetterstabDefined in optics-core-0.4.1.1 · Optics.Indexed.Core
(s~t, a~b) => IxOpticAn_AffineFoldstabDefined in optics-core-0.4.1.1 · Optics.Indexed.Core
Monoid structures
0 declarations
There are two ways to combine (possibly indexed) folds, traversals and
related optics with the same outer and inner types:
Visit all the targets of the first optic, then all the targets of the
second optic. This makes sense for folds (summing or isumming) and
traversals (adjoin or iadjoin), provided in the latter case that the
targets are disjoint.
Visit the targets of the first optic if there are any, or if not, visit the
targets of the second optic. This makes sense for folds (failing or
ifailing) and affine folds (afailing or iafailing).
These operations form monoid structures on the appropriate optic kinds, with
the identity element ignored, which visits no targets.
There is no Semigroup or Monoid instance for Optic, because there is
not a unique choice of monoid to use, and the (<>) operator could not be
used to combine optics of different kinds. When porting code from lens that
uses (<>) to combine folds, use summing instead.
Generate a Prism for each constructor of a data type and combine them
into a single class. No Isos are created. Reviews are created for
constructors with existentially quantified constructors and GADTs.
e.g.
data FooBarBaz a
= Foo Int
| Bar a
| Baz Int Char
makeClassyPrisms ''FooBarBaz
will create
class AsFooBarBaz s a | s -> a where
_FooBarBaz :: Prism' s (FooBarBaz a)
_Foo :: Prism' s Int
_Bar :: Prism' s a
_Baz :: Prism' s (Int,Char)
_Foo = _FooBarBaz % _Foo
_Bar = _FooBarBaz % _Bar
_Baz = _FooBarBaz % _Baz
instance AsFooBarBaz (FooBarBaz a) a
Generate an As class of prisms. Names are selected by prefixing the
constructor name with an underscore. Constructors with multiple fields will
construct Prisms to tuples of those fields.
Generate a Prism for each constructor of a data type. Isos generated when
possible. Reviews are created for constructors with existentially quantified
constructors and GADTs.
e.g.
data FooBarBaz a
= Foo Int
| Bar a
| Baz Int Char
makePrisms ''FooBarBaz
will create
_Foo :: Prism' (FooBarBaz a) Int
_Bar :: Prism (FooBarBaz a) (FooBarBaz b) a b
_Baz :: Prism' (FooBarBaz a) (Int, Char)
Field rules fields in the form prefixFieldname or _prefixFieldname
If you want all fields to be lensed, then there is no reason to use an _ before the prefix.
If any of the record fields leads with an _ then it is assume a field without an _ should not have a lens created.
Note that prefix may be any string of characters that are not uppercase
letters. (In particular, it may be arbitrary string of lowercase letters
and numbers) This is the behavior that defaultFieldRules had in lens
4.4 and earlier.
Field rules for fields in the form prefixFieldname or _prefixFieldname
If you want all fields to be lensed, then there is no reason to use an _
before the prefix. If any of the record fields leads with an _ then it is
assume a field without an _ should not have a lens created.
Note: The prefix must be the same as the typename (with the first
letter lowercased). This is a change from lens versions before lens 4.5. If
you want the old behaviour, use makeLensesWithabbreviatedFields
Field rules for fields in the form _fieldname (the leading
underscore is mandatory).
Note: The primary difference to camelCaseFields is that for
classUnderscoreNoPrefixFields the field names are not expected to
be prefixed with the type name. This might be the desired behaviour
when the DuplicateRecordFields extension is enabled.
Rules for making lenses and traversals that precompose another Lens using
a custom function for naming the class, main class method, and a mapping from
field names to definition names.
For each record in the declaration quote, make lenses and traversals for
it, and create a class when the type has no arguments. All record syntax
in the input will be stripped off.
e.g.
declareClassy [d|
data Foo = Foo { fooX, fooY :: Int }
deriving Show
|]
will create
data Foo = Foo IntInt deriving Show
class HasFoo t where
foo :: Lens' t Foo
instance HasFoo Foo where foo = id
fooX, fooY :: HasFoo t => Lens' t Int
Make field optics as labels for all records in the given declaration
quote. All record syntax in the input will be stripped off.
e.g.
declareLenses [d|
data Dog = Dog { name :: String, age :: Int }
deriving Show
|]
will create
data Dog = Dog String Int
deriving Show
instance (k ~ A_Lens, ...) => LabelOptic "name" k Dog Dog ...
instance (k ~ A_Lens, ...) => LabelOptic "age" k Dog Dog ...
> undefined & x .~ 8 & y .~ True
Foo {_x = 8, _y = True}
The downside of this flag is that it can lead to space-leaks and
code-size/compile-time increases when generated for large records. By default
this flag is turned off, and strict optics are generated.
When using lazy optics the strict optic can be recovered by composing with
equality':
Generate "updateable" optics when True. When False, (affine) folds will
be generated instead of (affine) traversals and getters will be generated
instead of lenses. This mode is intended to be used for types with invariants
which must be maintained by "smart" constructors.
Make lenses and traversals for a type, and create a class when the type has
no arguments.
e.g.
data Foo = Foo { _fooX, _fooY :: Int }
makeClassy ''Foo
will create
class HasFoo c where
foo :: Lens' c Foo
fooX :: Lens' c Int
fooY :: Lens' c Int
fooX = foo % fooX
fooY = foo % fooY
instance HasFoo Foo where
foo = lensVL id
fooX = lensVL $ \f s -> case s of
Foo x1 x2 -> fmap (\y -> Foo y x2) (f x1)
fooY = lensVL $ \f s -> case s of
Foo x1 x2 -> fmap (\y -> Foo x1 y) (f x2)
Make lenses and traversals for a type, and create a class when the type has
no arguments. Works the same as makeClassy except that (a) it expects that
record field names do not begin with an underscore, (b) all record fields are
made into lenses, and (c) the resulting lens is prefixed with an underscore.
Build field optics as instances of the LabelOptic class for use with
overloaded labels. See Optics.Label for how to use this pattern.
e.g.
data Animal
= Cat { animalAge :: Int
, animalName :: String
}
| Dog { animalAge :: Int
, animalAbsurd :: forall a b. a -> b
}
makeFieldLabels ''Animal
will create
instance
(k ~ A_Lens, a ~ Int, b ~ Int
) => LabelOptic "age" k Animal Animal a b where
labelOptic = lensVL $ \f s -> case s of
Cat x1 x2 -> fmap (\y -> Cat y x2) (f x1)
Dog x1 x2 -> fmap (\y -> Dog y x2) (f x1)
instance
(k ~ An_AffineTraversal, a ~ String, b ~ String
) => LabelOptic "name" k Animal Animal a b where
labelOptic = atraversalVL $ \point f s -> case s of
Cat x1 x2 -> fmap (\y -> Cat x1 y) (f x2)
Dog x1 x2 -> point (Dog x1 x2)
instance
( Dysfunctional "absurd" k Animal Animal a b
, k ~ An_AffineFold, a ~ (x -> y), b ~ (x -> y)
) => LabelOptic "absurd" k Animal Animal a b where
labelOptic = afolding $ \s -> case s of
Cat _ _ -> Nothing
Dog _ f -> Just f
which can be used as #age, #name and #absurd with the
OverloadedLabels language extension.
Note: if you wonder about the structure of instances, see
Optics.Label#structure.
data Foo a = Foo { _fooX :: Int, _fooY :: a }
newtype Bar = Bar { _barX :: Char }
makeFields ''Foo
makeFields ''Bar
will create
class HasX s a | s -> a where
x :: Lens' s a
instance HasX (Foo a) Int where
x = lensVL $ \f s -> case s of
Foo x1 x2 -> fmap (\y -> Foo y x2) (f x1)
class HasY s a | s -> a where
y :: Lens' s a
instance HasY (Foo a) a where
y = lensVL $ \f s -> case s of
Foo x1 x2 -> fmap (\y -> Foo x1 y) (f x2)
instance HasX Bar Char where
x = lensVL $ \f s -> case s of
Bar x1 -> fmap (\y -> Bar y) (f x1)
Generate overloaded field accessors based on field names which
are only prefixed with an underscore (e.g. _name), not
additionally with the type name (e.g. _fooName).
This might be the desired behaviour in case the
DuplicateRecordFields language extension is used in order to get
rid of the necessity to prefix each field name with the type name.
As an example:
data Foo a = Foo { _x :: Int, _y :: a }
newtype Bar = Bar { _x :: Char }
makeFieldsNoPrefix ''Foo
makeFieldsNoPrefix ''Bar
will create classes
class HasX s a | s -> a where
x :: Lens' s a
class HasY s a | s -> a where
y :: Lens' s a
together with instances
instance HasX (Foo a) Int
instance HasY (Foo a) a where
instance HasX Bar Char where
Build field optics as top level functions with a sensible default
configuration.
e.g.
data Animal
= Cat { _age :: Int
, _name :: String
}
| Dog { _age :: Int
, _absurd :: forall a b. a -> b
}
makeLenses ''Animal
will create
absurd :: forall a b. AffineFold Animal (a -> b)
absurd = afolding $ \s -> case s of
Cat _ _ -> Nothing
Dog _ x -> Just x
age :: Lens' Animal Int
age = lensVL $ \f s -> case s of
Cat x1 x2 -> fmap (\y -> Cat y x2) (f x1)
Dog x1 x2 -> fmap (\y -> Dog y x2) (f x1)
name :: AffineTraversal' Animal String
name = atraversalVL $ \point f s -> case s of
Cat x1 x2 -> fmap (\y -> Cat x1 y) (f x2)
Dog x1 x2 -> point (Dog x1 x2)
Field rules for fields without any prefix. Useful for generation of field
labels when paired with DuplicateRecordFields language extension so that no
prefixes for field names are necessary.
A FieldNamer that leaves the field name as-is. Useful for generation of
field labels when paired with DuplicateRecordFields language extension so
that no prefixes for field names are necessary.
An overloaded label #foo can be used as an optic if there is an instance
LabelOptic "foo" k s t a b.
Alternatively, if both s and t have a Generic (GenericLabelOptics if
explicit-generic-labels flag is enabled) instance, a total field of s is
accessible by a label #field of kind A_Lens, whereas its constructor by a
label #_Constructor of kind A_Prism.
(k~An_Iso, a~Void0, b~Void0) => LabelOpticnamekVoid0Void0abDefined in optics-core-0.4.1.1 · Optics.Label
If for an overloaded label #label there is no instance starting with
LabelOptic "label" in scope, using it in the context of optics makes GHC
immediately pick the overlappable instance defined below (since no other
instance could match). If at this point GHC has no information about s or
t, it ends up picking incoherent instance of GenericLabelOptic defined
below. Prevent that (if only to be able to inspect most polymorphic types of
#foo % #bar or view #foo in GHCi) by defining a dummy instance that
matches all names, thus postponing instance resolution until s or t is
known.
If the explicit-generic-labels Cabal flag is enabled, only types with
this instance (which can be trivially derived with DeriveAnyClass
extension) will be able to use labels as generic optics with a specific type.
It's an option for application developers to disable implicit fallback to
generic optics for more control.
Libraries using generic labels with their data types should derive this
instance for compatibility with the explicit-generic-labels flag.
Note: the flag explicit-generic-labels is disabled by default. Enabling
it is generally unsupported as it might lead to compilation errors of
dependencies relying on implicit fallback to generic optics.
For setting/modifying using a Setter, a variety of combinators are available
in Optics.State and Optics.State.Operators. The latter are not exported
by the main Optics module, so must be imported explicitly.