Lenses You Can Make at Home
April 26, 2014
Contents
The most striking traits of the lens
library are its astonishing breadth and generality. And yet, the whole edifice is built around van Laarhoven lenses, which are a simple and elegant concept. In this hands-on exposition, I will show how the Lens
type can be understood without prerequisites other than a passing acquaintance with Haskell functors. Encouraging sound intuition in an accessible manner can go a long way towards making lens
and lenses less intimidating.
Humble Beginnings
Dramatis personæ:
import Data.Functor.Identity (Identity(..))
import Control.Applicative (Const(..))
I will define a toy data type so that we have something concrete to play with, as well as a starting point for working out generalisations.
data Foo = Foo { bar :: Int } deriving (Show)
The record definition gets us a function for accessing the bar
field.
GHCi> :t bar
bar :: Foo -> Int
As for the setter, we have to define it ourselves, unless we feel like mucking around with record update syntax.
setBar :: Foo -> Int -> Foo
= x { bar = y } setBar x y
Armed with a proper getter and setter pair, we can easily flip the sign of the bar
inside a Foo
.
GHCi> let x = Foo 3
GHCi> setBar x (negate $ bar x)
Foo {bar = -3}
We can make it even easier by defining a modifier function for bar
.
modifyBar :: (Int -> Int) -> Foo -> Foo
= setBar x . k . bar $ x modifyBar k x
GHCi> modifyBar negate x
Foo {bar = -3}
setBar
can be recovered from modifyBar
by using const
to discard the original value and put the new one in its place.
const y = \_ -> y
setBar' :: Foo -> Int -> Foo
= modifyBar (const y) x setBar' x y
If our data type had several fields, defining a modifier for each of them would amount to quite a lot of boilerplate. We could minimise it by, starting from our modifyBar
definition, abstracting from the specific getter and setter for bar
. Here, things begin to pick up steam. I will define a general modify
function, which, given an appropriate getter-setter pair, can deal with any field of any data type.
modify :: (s -> a) -> (s -> a -> s) -> (a -> a) -> s -> s
= setter x . k . getter $ x modify getter setter k x
It is trivial to recover modifyBar
; when we do so, s
becomes Foo
and a
becomes Int
.
modifyBar' :: (Int -> Int) -> Foo -> Foo
= modify bar setBar modifyBar'
Functors Galore
The next step of generalisation is the one leap of faith I will ask of you in the way towards lenses. I will introduce a variant of modify
in which the modifying function, rather than being a plain a -> a
function, returns a functorial value. Defining it only takes an extra fmap
.
modifyF :: Functor f => (s -> a) -> (s -> a -> s)
-> (a -> f a) -> s -> f s
= fmap (setter x) . k . getter $ x modifyF getter setter k x
And here is its specialisation for bar
.
modifyBarF :: Functor f => (Int -> f Int) -> Foo -> f Foo
= modifyF bar setBar modifyBarF
Why on Earth we would want to do that? For one, it allows for some nifty tricks depending on the functor we choose. Let’s try it with lists. Specialising the modifyF
type would give:
modifyL :: (s -> a) -> (s -> a -> s) -> (a -> [a]) -> s -> [s]
Providing the getter and the setter would result in a (a -> [a]) -> s -> [s]
function. Can you guess what it would do?
GHCi> modifyBarF (\y -> [0..y]) x
Foo {bar = 0},Foo {bar = 1},Foo {bar = 2},Foo {bar = 3}] [
As the types suggest, we get a function which modifies the field in multiple ways and collects the results.
I claimed that moving from modify
to modifyF
was a generalisation. Indeed, we can recover modify
by bringing Identity
, the dummy functor, into play.
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity x) = Identity (f x)
modifyI :: (s -> a) -> (s -> a -> s) -> (a -> Identity a) -> s -> Identity s
modify' :: (s -> a) -> (s -> a -> s) -> (a -> a) -> s -> s
=
modify' getter setter k . modifyF getter setter (Identity . k) runIdentity
We wrap the field value with Identity
value after applying k
and unwrap the final result after applying the setter. Since Identity
does nothing interesting to the wrapped values, the overall result boils down to our original modify
. If you have found this definition confusing, I suggest that you, as an exercise, rewrite it in pointful style and substitute the definition of modifyF
.
We managed to get modify
back with little trouble, which is rather interesting. However, what is truly surprising is that we can reconstruct not only the modifier but also the getter! To pull that off, we will use Const
, which is a very quaint functor.
newtype Const a b = Const { getConst :: a }
instance Functor (Const a) where
fmap _ (Const y) = Const y
modifyC :: (s -> a) -> (s -> a -> s) -> (a -> Const r a) -> s -> Const r s
If functors were really containers, Const
would be an Acme product. A Const a b
value does not contain anything of type b
; what it does contain is an a
value that we cannot even modify, given that fmap f
is id
regardless of what f
is. As a consequence, if, given a field of type a
, we pick Const a
as the functor to use with modifyF
and use the modifying function to wrap the field value with Const
, then the value will not be affected by the setter, and we will be able to recover it later. That suffices for recovering the getter.
get :: (s -> a) -> (s -> a -> s) -> s -> a
= getConst . modifyF getter setter Const
get getter setter
getBar :: Foo -> Int
= get bar setBar getBar
The Grand Unification
Given a getter and a setter, modifyF
gets us a corresponding functorial modifier. From it, by choosing the appropriate functors, we can recover the getter and a plain modifier; the latter, in turn, allows us to recover the setter. We can highlight the correspondence by redefining once more the recovered getters and modifiers, this time in terms of the functorial modifier.
modifyF :: Functor f => (s -> a) -> (s -> a -> s)
-> ((a -> f a) -> s -> f s)
modify'' :: ((a -> Identity a) -> s -> Identity s) -> (a -> a) -> s -> s
= runIdentity . modifier (Identity . k)
modify'' modifier k
modifyBar'' :: (Int -> Int) -> Foo -> Foo
= modify'' modifyBarF
modifyBar''
set :: ((a -> Identity a) -> s -> Identity s) -> s -> a -> s
= modify'' modifier (const y) x
set modifier x y
setBar'' :: Foo -> Int -> Foo
= set modifyBarF
setBar''
get' :: ((a -> Const a a) -> s -> Const a s) -> (s -> a)
= getConst . modifier Const
get' modifier
getBar' :: Foo -> Int
= get' modifyBarF getBar'
The bottom line is that given modifyBarF
we can get by without modifyBar
, setBar
and bar
, as modify''
, set
and get'
allow us to reconstruct them whenever necessary. While our first version of get
was, in effect, just a specialised const
with a wacky implementation, get'
is genuinely useful because it cuts the number of separate field manipulation functions we have to deal with by a third.
Expanding Horizons
Even after all of the work so far we can still generalise further! Let’s have a second look at modifyF
.
modifyF :: Functor f => (s -> a) -> (s -> a -> s)
-> (a -> f a) -> s -> f s
= fmap (setter x) . k . getter $ x modifyF getter setter k x
The type of setter
is (s -> a -> s)
; however, nothing in the implementation forces the first argument and the result to have the same type. Furthermore, with a different signature k
could have a more general type, (a -> f b)
, as long as the type of setter
was adjusted accordingly. We can thus give modifyF
a more general type.
modifyGenF :: Functor f => (s -> a) -> (s -> b -> t)
-> (a -> f b) -> s -> f t
= fmap (setter x) . k . getter $ x modifyGenF getter setter k x
For the sake of completeness, here are the generalised recovery functions. get
is not included because the generalisation does not affect it.
modifyGen :: ((a -> Identity b) -> s -> Identity t) -> (a -> b) -> s -> t
= runIdentity . modifier (Identity . k)
modifyGen modifier k
setGen :: ((a -> Identity b) -> s -> Identity t) -> s -> b -> t
= modifyGen modifier (const y) x setGen modifier x y
By now, it is clear that our getters and setters need not be ways to manipulate fields in a record. In a broader sense, a getter is anything that produces a value from another; in other words, any function can be a getter. By the same token, any binary function can be a setter, as all that is required is that it combines one value with another producing a third; the initial and final values do not even need to have the same type.1 That is a long way from the toy data type we started with!
The Reveal
If we look at modifyGenF
as a function of two arguments, its result type becomes:
Functor f => (a -> f b) -> s -> f t
Now, let’s take a peek at Control.Lens.Lens:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
It is the same type! We have reached our destination.2 A lens is what we might have called a generalised functorial modifier; furthermore, sans implementation details we have that:
- The
lens
function ismodifyGenF
; modifyF
islens
specialised to produce simple lenses;3modifyBarF
is a lens with typeLens Foo Foo Int Int
;(^.)
is flippedget'
;set
issetGen
;over
ismodifyGen
further generalised.4
lens
uses type synonyms liberally, so those correspondences are not immediately obvious form the signatures in the documentation. Digging a little deeper, however, shows that in
set :: ASetter s t a b -> b -> s -> t
ASetter
is merely
type ASetter s t a b = (a -> Identity b) -> s -> Identity t
Analogously, we have
(^.) :: s -> Getting a s a -> a
type Getting r s a = (a -> Const r a) -> s -> Const r s
Behind the plethora of type synonyms - ASetter
, Getting
, Fold
, Traversal
, Prism
, Iso
and so forth - there are different choices of functors,5 which make it possible to capture many different concepts as variations on lenses. The variations may be more general or less general than lenses; occasionally they are neither, as the overlap is just partial. The fact that we can express so much through parametrization of functors is key to the extraordinary breadth of lens
.
Going Forward
This exposition is primarily concerned with building lenses, and so very little was said about how to use them. In any case, we have seen enough to understand why lenses are also known as functional references. By unifying getters and setters, lenses provide a completely general vocabulary to point at parts of a whole.
Finally, a few words about composition of lenses are unavoidable. One of the great things about lenses is that they are just functions; even better, they are functions with signatures tidy enough for them to compose cleanly with (.)
. That makes it possible to compose lenses independently of whether you intend to get, set or modify their targets. Here is a quick demonstration using the tuple lenses from lens
.
GHCi> :m
GHCi> :m +Control.Lens
GHCi> ((1,2),(3,4)) ^. _1 . _2
GHCi> 2
GHCi> set (_1 . _2) 0 ((1,2),(3,4))
GHCi> ((1,0),(3,4))
A perennial topic in discussions about lens
is the order of composition of lenses. They are often said to compose backwards; that is, backwards with respect to composition of record accessors and similar getters. For instance, the getter corresponding to the _1 . _2
lens is snd . fst
. The claim that lenses compose backwards, or in the “wrong order”, however, are only defensible when talking about style, and not about semantics. That becomes clear after placing the signatures of a getter and its corresponding lens side by side.
GHCi> :t fst
fst :: (a, b) -> a
GHCi> :t _1 :: Lens' (a, b) a
_1 :: Lens' (a, b) a
:: Functor f => (a -> f a) -> (a, b) -> f (a, b)
The getter takes a value of the source type and produces a value of the target type. The lens, however, takes a function from the target type and produces a function from the source type. Therefore, it is no surprise that the order of composition differs, and the order for lenses is entirely natural. That ties in closely to what we have seen while implementing lenses. While we can squeeze lenses until they give back getters, it is much easier to think of them as generalised modifiers.
We are not quite as free when it comes to pairing getters and setters. Beyond the obvious need for getter and setter to start from values of the same type, they should behave sanely when composed. In particular, the following should hold:
↩︎get' modifier (setGen modifier y x) ≡ y setGen modifier (get' modifier x) x ≡ x setGen modifier z (setGen modifier y x) ≡ setGen modifier z x
“What about the
forall
?” you might ask. Are we cheating? Not quite. Theforall
is there to control howf
is specialised when lens combinators are used. The underlying issue does not affect our reasoning here. If you are into type system subtleties, there were a few interesting comments about it in the reddit thread for this post.↩︎Lens' s a
orLens s s a a
, as opposed toLens s t a b
.↩︎Yes, even further; from taking modifying functions to taking modifying profunctors. The difference need not worry us now.↩︎
And in some cases of profunctors to replace the function type constructor.↩︎
Post licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.