Haskell Lenses Notes

Posted on February 28, 2016

Last updated on February 28, 2016

This post contains some notes from watching this great video by Simon Peyton Jones on what a lens in Haskell is. If you are interested, I recommend you watch the video, as the following are just my notes for future reference.

Very simply, a lens is something that allows you to access and modify parts of a data-structure.

Before continuing, we will need some imports and GHC extensions.
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}

import Data.Functor
import Data.Functor.Identity
import Control.Applicative

Suppose you have the following record types and an example fred.

data Employee = Employee {
  _name :: String,
  _salary :: Int,
  _address :: Address
} deriving (Eq, Show)

data Address = Address {
  _street :: String,
  _number :: Int
} deriving (Eq, Show)

fred = Employee "Fred" 100 (Address "Fifth Avenue" 120)

With lenses, our goal is to be able to access and modify parts of these records, say, update the street name of where Fred lives.

We need two things: a way to view part of a record, and a way to set part of a record. Given a structure s and a focus a, we come up with the following:

data LensR s a = LensR {
  viewR :: s -> a,
  setR :: a -> s -> s
}

The function viewR on a LensR s a takes a structure s and returns the value of the focus. Let’s make a lens that allows us to view/set the name of the employee.

name :: LensR Employee String
name = LensR {
  viewR = _name,
  setR = \a s -> s {_name = a}
}

Let’s try using it by changing Fred’s name to “Bob”.

λ> setR name "Bob" fred
Employee {_name = "Bob", _salary = 100, _address = Address {_street = "Fifth Avenue", _number = 120}}

That looks like it worked! Let’s make some more lenses. One for the address field, and one for the street of the address.

street :: LensR Address String
street = LensR {
  viewR = _street,
  setR = \a s -> s {_street = a}
}

address :: LensR Employee Address
address = LensR {
  viewR = _address,
  setR = \a s -> s {_address = a}
}

Now, a nice property of lenses would be if they composed. For example, say you want to change the street Fred lives on. You will need a composeR function of type LensR s1 s2 -> LensR s2 a -> LensR s1 a. Here’s how it will look. It’s really nothing special, you just need a little bit of thought. A view of the composition is the composition of the views, and a set of a composition is first viewing the underlying record, updating it with its set function, and then setting the whole thing back into the larger structure.

composeR :: LensR s1 s2 -> LensR s2 a -> LensR s1 a
composeR (LensR v1 s1) (LensR v2 s2) = LensR {
  viewR = v2 . v1,
  setR = \a s -> s1 (s2 a (v1 s)) s
}

Let’s confirm it works.

λ> viewR (composeR address street) fred
"Fifth Avenue"

λ> setR (composeR address street) "Fourth Avenue" fred
Employee {_name = "Fred", _salary = 100, _address = Address {_street = "Fourth Avenue", _number = 120}}

This is all well and good. But now suppose you want to do something more complicated, like update a field with a function. This operation would have the type (a -> a) -> s -> s. What if you needed the function to do some possibly failing computation? The type would then be (a -> Maybe a) -> s -> Maybe s. What if it needed IO? We would get a -> IO a) -> s -> IO s.

This brings us to the following main idea of the person who invented lenses.

type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s

In fact, this function type is powerful enough to represent LensR that we developed above. The two are isomorphic. If two types a and b are isomorphic, it means there exist functions f :: a -> b and g :: b -> a such that f . g = id and g . f = id. Try figuring out the isomorphism by yourself before continuing.

Let’s show that we can get from a Lens' to a LensR'. We start withset`.

set :: Lens' s a -> (a -> s -> s)
{-set ln a s = runIdentity (ln set_fld s)-}
  {-where-}
    {-set_fld _ = Identity a-}
set ln a = runIdentity . ln (Identity . const a)

How does this work? It works by picking the Functor f in the above typealias to be the Identity functor! The function set_fld takes the type a -> Identity a, ignores the current value, and sets it to whatever set got. In the above, you see two versions that are equivalent, just the second one eta abstracts the s.

What about viewing? In a similar fashion, we will use the Const v functor.

view :: Lens' s a -> (s -> a)
{-view ln s = getConst (ln Const s)-}
view ln = getConst . ln Const

The function between Lens' s a and LensR s a is then simply:

fromLens' :: Lens' s a -> LensR s a
fromLens' ln = LensR {
  viewR = view ln,
  setR = set ln
}

It is now also easy to create a function that we talked about before of type (a -> a) -> s -> s.

over :: Lens' s a -> (a -> a) -> s -> s
{-over ln f s = runIdentity (ln set_fld s)-}
  {-where-}
    {-set_fld a = Identity $ f a-}
{-over ln f s = runIdentity (ln (\a -> Identity $ f a) s)-}
{-over ln f s = runIdentity $ ln (Identity . f) s-}
over ln f = runIdentity . ln (Identity . f) 

Let’s do the opposite direction now. Try it yourself first. There really isn’t much you can do for the types to match up.

fromLensR :: LensR s a -> Lens' s a
fromLensR (LensR v u) = \f s -> fmap (flip u $ s) (f $ v s)

We can test it out.

λ> view (fromLensR name) fred
"Fred"

λ> set (fromLensR name) "Bob" fred
Employee {_name = "Bob", _salary = 100, _address = Address {_street = "Fifth Avenue", _number = 120}}

λ> viewR (fromLens' $ fromLensR name) fred
"Fred"

Cool! That works. But how do you actually make a Lens'?

name' :: Lens' Employee String
name' f e =
  fmap (\n -> e {_name = n}) (f $ _name e)

Think of the f as a function that takes a new value for a field, and applies it to a record with a hole there. You can easily construct these with template haskell. This is what happens in the Control.Lens library.

How about Lens' composition? Look at the types. Conveniently, as Lens' are just functions, their composition is just pure function composition.

composeLens' :: Lens' s1 s2 -> Lens' s2 a -> Lens' s1 a
composeLens' = (.)

The fantastic thing is that all this will work for any functor.

Let’s look at two applications that Simon noted in his talk. First, how about virtual/derived fields? The following record only stores temperature as Fahrenheit, but we can make a celcius lens that allows us to view and set the temperature as if it was in degrees celcius.

data Temp = Temp { _fahrenheit :: Float } deriving (Eq, Show)

fahrenheit :: Lens' Temp Float
fahrenheit f t = fmap (\temp -> t {_fahrenheit = temp}) (f $ _fahrenheit t)

centigrade :: Lens' Temp Float
centigrade f (Temp fahren) = fmap (\centi -> Temp (cToF centi)) (f $ fToC fahren)

cToF :: Float -> Float
cToF c = c*9 / 5 + 32

fToC :: Float -> Float
fToC f = (f-32)*5 / 9
λ> view centigrade (Temp 100)
37.77778

λ> set centigrade 100 (Temp 100)
Temp {_fahrenheit = 212.0}
λ>

Another example is maintaining variants. In the following data structure, we store hours and minutes. What would happen though if we added 20 minutes to a record that already has 50 minutes? It would overflow. We can maintain the invariants with a lens that looks like this.

data Time = Time {
  _hours :: Int,
  _mins :: Int
} deriving (Eq, Show)

mins :: Lens' Time Int
mins f (Time h m) = fmap wrap (f m)
  where 
    wrap :: Int -> Time
    wrap m' = let (di,mo) = (m' `divMod` 60) in Time (h + di) mo

Check it out:

λ> over mins (+20) timeNow
Time {_hours = 4, _mins = 10}

Conclusion

I hope this was useful, even if just a very brief introduction to lenses in Haskell.

Markdown SHA1: 08b5c71fafc4132754d68da77ec0dd0e3576a82e