Record Syntax, Lenses and Prisms: Part 2 - Lenses
Lenses
Lenses are a quite interesting idea first mentioned by Twan van Laarhoven and have lived through a few implementations until the lens
-library by Ed Kmett has proven to be the stable solution for now. It has a batteries included approach and provides many operators and a template haskell convention to generate lenses for your own algebraic data types.
The convention is to put an _
at the beginning of the record names in the record syntax definition. And then use the magic of template haskell to generate the corresponding lenses a.k.a. functional getters and setters with makeLenses ''MyADT
.
Going back to the old examples one could rewrite it as follows.
For one we need the template haskell language pragma to make the magic work.
And we also need the lens library to be installed for which I recommend using cabal
, the interface to the haskell packaging system.
~ $ cabal update
~ $ cabal install lens
... this may take some time so do something healthy like eat an apple or stretching until you get:
Installed lens-4.3.3 (the current lens version as of 11th of August 2014)
Next step is to do the import of the lens package and add a whole bunch of underscores …
> import Control.Lens
> data Human = Attributes { _name :: String, _body :: Body, _age :: Int} deriving (Show)
> data Body = Body { _hat :: Maybe Hat
> , _beard :: Maybe Beard
> , _torso :: Torso
> , _accessories :: [Accessories]}
> data Pirate = Captain { _attributes :: Human, _ship :: String}
> | FirstMate { _attributes :: Human, _shanty :: String}
> | Marauder { _attributes :: Human, _hometown :: String}
… and of course create the lenses. (In the background template haskell now creates functions attributes, ship, shanty and so on.)
Now would be a great moment to talk about the types of Lens and the famous Lens-laws, but I’d rather have some use of them before I bore you to death.
So let us have a look at the instance declaration for Show
> instance Show Pirate where
> show (Captain a s) = "The infamous Captain "++ a^.name++" of the "++ s++"\n"
> ++ show (a^.body)++"\n"
> ++ "\t Age: "++show (a^.age)
I wouldn’t call that an improvement but the average object oriented programmer might. So what is this ^.
operator, it is an alias for the view
-function that can focus on the parts of a lens.
The type signature for (^.)
is a bit complicated, but if we combine it with the generated functions we see
GHCi> :t (^.attributes)
(^.attributes) :: Pirate -> Human
GHCi> :t (^.attributes.name)
(^.attributes.name) :: Pirate -> String
But where the combined type signature of (^.)
and attributes
is simple their own type signature is - let’s just call it not simple.
GHCi> :t (^.)
(^.) :: s -> Getting a s a -> a
GHCi> :t attributes
attributes :: Functor f => (Human -> f Human) -> Pirate -> f Pirate
As we saw we have something called Getting
so there should also be some setting stuff and indeed we have a set
-function and the infix alias (.~)
which is more useful with the &
-operator which is just reverse function application: x & f = f x
.
A few examples should provide a bit more insight…
> hel = cpt & attributes.name.~ "Hellscream"
GHCi > hel
The infamous Captain Hellscream of the SS Sea Serpent
Hat: Just Tricorne
...
Age: 42
… and see that now it is really easy to undress our pirates with:
> undress pirate = pirate & attributes . body . hat .~ Nothing
> & attributes . body . beard .~ Nothing
> & attributes . body . torso .~ Naked
> & attributes . body . accessories .~ []
So we have getters and setters, but we want more we want to use functions, this is where the over
function or the (%~)
-operator comes into play.