Isomorphism lenses
I would be interested in a small exam开发者_如何学Gople 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.
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:
fw piLens . bw piLens = id
bw piLens . fw piLens = id
Both proofs follow directly from the definitions of piFwd
and piBwd
and laws about composition.
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.
精彩评论