functional optics

Isaac Newton — Opticks
Functional optics are a family of programming idioms which uses function composition to express data access and manipulation. Object oriented programming includes a similar mechanism with the (more commonly known) use of getter and setter functions. The composition allowed by functional optics is particularly expressive, especially used in combination with typed functions and immutable data structures. Optics functions borrow names by related metaphors from the optics of light.

utility

Consider one data structure which holds, nests, another:

data A = { b : B }
data B = { x : Number }
... the outer structure might be constructed by
a = A (B 1)
... then de-constructed by
A (B x) = a
... where x = 1. While terse, functions written across this nesting become increasingly unweildy:
shiftA : A -> A
shiftA (A (B x)) = A (B (x + 1))
In this expression, deconstruction with immediate re-construction begins to occupy a large share of the implementation, obscuring the kernel of shiftA's meaning.
shiftA : A -> A
shiftA = over (b . x) (+ 1)
Here, motivating a core tool from functional optics: over (think: over-write) is a function focusing the b's x, allowing the more direct expression of the intended meaning. (More broadly, this is point-free.) The b and x, used for this focusing, are the lenses of functional optics.

Lenses

https://fluffynukeit.com/how-functional-programming-lenses-work/ https://hackage.haskell.org/package/lens-tutorial-1.0.5/docs/Control-Lens-Tutorial.html A Lens, at its simplist, can be expressed as the pair view and over:
data Lens s a =
  { view : s -> a
  , over : (a -> a) -> (s -> s)
  }
A Lens s a can be read as focusing a from s, where a is some smaller nested part of the larger s. view will get from s the focus, over is slightly more invovled: it uses an update to the focus, a -> a, yeilding a change to an s. Consider the simpler case of over that doesn't update the focus, just sets it to a new value:
set : a -> (s -> s)
... this is over with a -> a being constant, ignoring the prior value of the focus:
set a s = over (const a) s

encodings

The properties of Lens can be re-expressed in multiple forms:
type Lens s a
  =  forall f. Functor f
  => (a -> f a) -> s -> f s
... this encoding, Van Laarhoven's encoding, is less clear than the above presentation: the fundamental view and over are implicit, requiring the properties of Functor to be understood (see Appendix.) The expression using only functions does, however, allow for the more obvious use of composition.

composition

Lenses, by themselves, are not particularly expressive; their utility comes from the ease of their composition.

Two lenses, Lens a b and Lens b c combine for a Lens a c; in function encodings like Van Laarhoven's, this composition is standard the composition of functions (.):

(.) : Functor f
  => ((b -> f b) -> (a -> f a))
  -> ((c -> f c) -> (b -> f b))
  -> ((c -> f c) -> (a -> f a))
... which combine with view and over to form the distributivity laws of lenses:
view (lens1 . lens2) = (view lens2) . (view lens1)
over (lens1 . lens2) = (over lens1) . (over lens2)
view id = id
over id = id

further

There are further concepts in functional optics generalized to TODO

lab

register files with lenses

Register files with lenses explored the use of lenses in a computer architecture context, where multiple equivalent views into the same underlying data is particularly common.

references

usage

Example lifted from Control.Lens.Tutorial. (wayback)

appendix

Van Laarhoven encoding

The Van Laarhoven encoding,
type Lens = forall f. Functor f => (a -> f a) -> (s -> f s)
obscures the behavior of view and over, which are recovered by application of specific f. For f = Identity,
     : (a -> Identity a) -> (s -> Identity s)
over : (a -> a) -> (s -> s)
... over is restored; similarly, for f = Const a, applied with id : a -> a:
     : ((a -> Const a a) -> (s -> Const a s)) (a -> a)
     : ((a -> a) -> (s -> a)) (a -> a)
view : s -> a
... view is restored.