Vesa Karvonen
My special interest: Programming languages and techniques
``it is possible to make the structure of the program match the structure of the problem being solved.´´ — Burge
a bidirectional program than can be run
an element in/from a data structure.
A two-for-one bargain!
Forward
L.set(L.prop("x"), 2, {x: 1})
L.modify(L.prop("x"), x => x+1, {x: 1})
Backward
L.get(L.prop("x"), {x: 1})
Lenses are actually mapping functions:
map :: (a -> b) -> s -> t
Like list map:
map :: (a -> b) -> [a] -> [b]
Except lifted in some fashion, e.g.
map :: Functor f => (a -> f b) -> s -> f t
map :: Profunctor p => p a b -> p s t
Mapping functions:
mapList :: (a -> b) -> [a] -> [b]
mapLhs :: (a -> b) -> (a, c) -> (b, c)
compose:
mapListLhs :: (a -> b) -> [(a, c)] -> [(b, c)]
mapListLhs = mapList . mapLhs
mapLhsList :: (a -> b) -> ([a], c) -> ([b], c)
mapLhsList = mapLhs . mapList
And this is how we get the direction of lenses.
And others such as prisms, folds, getters, setters, ...
They can all share the same type (*)
And compose with each other
partial.lenses
which is my optics library and used in these slides:
But there are also many other optics libraries!
// Nest, Param, Organize, Removal, Traversal, Fold, Transform
const sampleTitles = {
titles: [{ language: "en", text: "Title" },
{ language: "sv", text: "Rubrik" }]}
L.get([], sampleTitles)
You have a data structure that...
...is more complex than just a single object or array.
And you need to update parts of that data structure.
Optics decouple the operation to perform on element(s) of a data structure from the details of selecting the element(s) and the details of maintaining data structure invariants.
In other words, a selection algorithm and data structure invariant maintenance can be expressed as a composition of optics and used with many different operations.
Compare to e.g. containers, iterators, and algorithms in the C++ STL.Atom
get
and set
LensedAtom
Kefir.Atom and Karet.Util libs
var state = U.atom({
titles: [{ language: "en", text: "Title" },
{ language: "sv", text: "Rubrik" }]})
var titleIn = language => ["titles",
L.find(R.whereEq({language})),
"text"]
var enTitle = U.view(titleIn("en"), state)
var svTitle = U.view(titleIn("sv"), state)
enTitle.log("en")
svTitle.log("sv")
state.modify(L.set(["titles", 0, "text"], "The title"))
There is a/are composition operator(s).
(+) :: T -> T -> T
There are simple universal laws that specify how they work.
x + (y + z) = (x + y) + z
zero :: T
x + zero = x
zero + x = x
Monoid is just an example!
L.compose(...os)
— Monoid (unityped)L.lazy(o => o)
— Fixed pointL.choices(...ls)
— SemigroupL.choice(...ls)
— MonoidL.chain(x => o, o)
— MonadPlusL.pick({...p:l})
— ProductL.branch({...p:t})
— CoproductL.seq(...ts)
— Monad
const elem = 2
const data = {x: 1}
const lens = "x"
const eq = (a, e) => R.equals(a, e) || a
R.identity({
GetSet: eq( L.set(lens, L.get(lens, data), data), data ),
SetGet: eq( L.get(lens, L.set(lens, elem, data)), elem )
})
(It can sometimes be useful to break some laws)
undefined
if non-existentundefined
if type-mismatchundefined
removes elementundefined
Propagating removal
Optimistic queries and updates
Prerequisite: You have a data structure to update
Optics can make it...
Comprehensive documentation.
Links to other libraries and resources.
https://github.com/calmm-js/partial.lenses/wikiAdditional material and examples.