Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Isomorphism lenses

Tags:

haskell

I would be interested in a small example of van Laarhoven's isomorphism lenses, applied to a data type like data BValue = BValue { π :: Float, σ :: Float, α :: Float } deriving Show (specifically, the get/set/modify functions). Thank you in advance.

like image 449
Arets Paeglis Avatar asked Aug 29 '11 17:08

Arets Paeglis


2 Answers

From van Laarhoven's post, the Lens type is

data Lens a b = forall r . Lens (Iso a (b, r))

So in our case a is BValue and we want to construct some leneses that pick out one or more of the elements. So for example, let's build a lens that picks out π.

piLens :: Lens BValue Float

So it's going to be a lens from a BValue to a Float (namely the first one in the triple, with label pi.)

piLens = Lens (Iso {fw = piFwd, bw = piBwd})

A lens picks out two things: a residual type r (omitted here because we don't have to explicitly specify an existential type in haskell), and an isomorphism. An isomorphism is in turn composed of a forward and a backward function.

piFwd :: BValue ->  (Float, (Float, Float))
piFwd (BValue {pi, sigma, alpha}) = (pi, (sigma, alpha))

The forward function just isolates the component that we want. Note that my residual type here is the "rest of the value", namely a pair of the sigma and alpha floats.

piBwd :: (Float, (Float, Float)) -> BValue
piBwd (pi, (sigma, alpha)) = BValue { pi = pi, sigma = sigma, alpha = alpha }

The backward function is analogous.

So now we have defined a lens for manipulating the pi component of a BValue.

The other seven lenses are similar. (7 lenses: pick out sigma and alpha, pick out all possible pairs (disregarding the order), pick out all of BValue and pick out ()).

The one bit I'm not sure about is strictness: I am a little worried that the fw and bw functions I have written are too strict. Not sure.

We are not done yet:

We still need to check that piLens actually respects the lens laws. The nice thing about van Laarhoven's definition of Lens is that we only have to check the isomorphism laws; the lens laws follow via the calculation in his blog post.

So our proof obligations are:

  1. fw piLens . bw piLens = id
  2. bw piLens . fw piLens = id

Both proofs follow directly from the definitions of piFwd and piBwd and laws about composition.

like image 94
2 revs Avatar answered Nov 18 '22 16:11

2 revs


Check out Data.Label from the fclabels package, which implements lenses for record types.

To illustrate this package, let's take the following two example datatypes.

import Data.Label
import Prelude hiding ((.), id)

data Person = Person
 { _name   :: String
 , _age    :: Int
 , _isMale :: Bool
 , _place  :: Place
 }

data Place = Place
 { _city
 , _country
 , _continent :: String
 }

Both datatypes are record types with all the labels prefixed with an underscore. This underscore is an indication for our Template Haskell code to derive lenses for these fields. Deriving lenses can be done with this simple one-liner:

$(mkLabels [''Person, ''Place])

For all labels a lens will created.

Now let's look at this example. This 71 year old fellow, my neighbour called Jan, didn't mind using him as an example:

jan :: Person
jan = Person "Jan" 71 True (Place "Utrecht" "The Netherlands" "Europe")

When we want to be sure Jan is really as old as he claims we can use the get function to get the age out as an integer:

hisAge :: Int
hisAge = get age jan

Consider he now wants to move to Amsterdam: what better place to spend your old days. Using composition we can change the city value deep inside the structure:

moveToAmsterdam :: Person -> Person
moveToAmsterdam = set (city . place) "Amsterdam"

And now:

ghci> moveToAmsterdam jan
Person "Jan" 71 True (Place "Amsterdam" "The Netherlands" "Europe")

Composition is done using the (.) operator which is part of the Control.Category module. Make sure to import this module and hide the default (.), id function from the Haskell Prelude.

like image 1
rampion Avatar answered Nov 18 '22 15:11

rampion