Pure profunctor lenses and optics via the profunctor representation theorem. You can read more about this representation in the following articles.
- “Profunctor optics: the categorical view”, Milewski (2017).
- “What you needa know about Yoneda”, Boisseau, Gibbons (2018).
- “Categories of optics”, Riley (2018).
- “Profunctor optics: a categorical update”, Clarke, Elkins, Gibbons, Loregian, Milewski, Pillmore, Román (2019).
All the optics on this library are particular cases of a single unified definition. All the combinators on this library are instantiations of the profunctor optic to a particular Tambara module.
The usual notions of lawfulness (as in “Categories of optics”) apply to non-mixed optics (lenses, prisms, traversals…). However, there is not a consensus on what the natural notion of laws for a mixed optic should be, apart from specific cases like monadic lenses.
The library follows, where possible, the style of combinators of the lens library. Example code from “Profunctor optics: a categorical update” works directly under the library.
Example 1. (Lenses and prisms) The composition of a prism (address
) and a lens (street
)
is used to parse a string and then access and modify one of its
subfields.
let home = "221b Baker St, London, UK"
address :: Prism Address String
street :: Lens String Address
>>> home?. address.street
Just "221b Baker St"
>>> home & address.street. ̃ "4 Marylebone Rd"
"4 Marylebone Rd, London, UK"
Example 2. (Monadic lenses) A polymorphic family of type-changing monadic lenses
for a logging monad is used to track each time a data holder
(Box
) is accessed.
box :: (Show b) => MonadicLens IO a b (Box a) (Box b)
>>> return (Box 42)
>>= mupdate box "hello"
>>= mupdate box "world"
[box]: contents changed to "hello".
[box]: contents changed to "world".
Box{"world"}
Example 3. (Algebraic lenses) A classifying lens (measure
) is used both for accessing
a the measurements of a point in the iris dataset and to classify
new measurements into a species (Versicolor
).
let iris =
[ Iris Setosa 4.9 3.0 1.4, 0.2
, Iris Setosa 4.7 3.2 1.3 0.2
, ...
, Iris Virginica 5.9 3.0 5.1 1.8 ]
measure :: AlgLens [] Measurements Flower
>>> iris!!4 ˆ. measure
(5.0, 3.6, 1.4, 0.2)
>>> iris & measure .? Measurements (4.8, 3.2, 3.5, 2.1)
Iris Versicolor (4.8, 3.1, 1.5, 0.1)
Example 4. (Traversals) The composition of a traversal (each
) with a prism
(address
) and a lens (city
) is used to parse a collection of strings
and modify one of their subfields.
let mail =
[ "43 Adlington Rd, Wilmslow, United Kingdom"
, "26 Westcott Rd, Princeton, USA"
, "St James's Square, London, United Kingdom"
]
each :: Traversal String [String]
address :: Prism Address String
city :: Lens String Address
>>> mail & each.address.city % ̃ uppercase
[ "43 Adlington Rd, WILMSLOW, United Kingdom"
, "26 Westcott Rd, PRINCETON, USA"
, "St James's Square, LONDON, United Kingdom"
]
Example 5. (Kaleidoscopes) Following the previous Example 3, a kaleidoscope
(aggregate
) is composed with an algebraic lens to create a new
point on the dataset by aggregating measurements with some function
(mean
, maximum
) and then classifying it.
measure :: AlgebraicLens [] Measurements Flower
aggregate :: Kaleidoscope Float Measurements
>>> iris & measure.aggregate >- mean
Iris Versicolor; Sepal (5.843, 3.054); Petal (3.758, 1.198)
This is a complete rewriting from the first prototype of vitrea, maintained by @emilypi and @mroman42.