> {-# LANGUAGE TemplateHaskell #-}
> import Data.Lens.Lazy > ( (~=), access, (%=), mapLens, (^=), (^.), (^%=), (^%%=), (^$) ) > import Data.Lens.Template ( makeLenses ) > import Data.Char ( toUpper ) > import Data.Map ( Map, fromList ) > import Control.Monad.State.Lazy ( Monad(return), State, runState )Next we define the all too familiar Person data type, which has an Address data type as one of its fields
> data Person = Person { > _fullName :: String, > _familiarName :: String, > _surName :: String, > _address :: Address } > > data Address = Address { > _which :: String, > _street :: String, > _city :: String, > _state :: String, > _zip :: String }I don't really like the default version of show for Person and Address, and it is easy enough to define your own.
> showsPerson :: Person -> String -> String > showsPerson (Person s1 s2 s3 a1) x = > concat ["\n",s1,"\n",s2," ",s3,"\n",show a1,x]
> showsAddress :: Address -> String -> String > showsAddress (Address s1 s2 s3 s4 s5) x = > concat [s1, ": ", > s2, "\n ", > s3, " ", > s4, " ", > s5, "\n", > x]
> instance Show Person where > showsPrec _ = showsPerson
> instance Show Address where > showsPrec _ = showsAddressHere we make TemplateHaskell do the dirty work, of defining the Lenses for us using the above field names.
> $( makeLenses [''Person, ''Address] )Here is some sample data
> henry = Person > "Henry Herman Laxen" > "Henry" > "Laxen" > (Address > "home" > "Via Alta #6" > "Chapala" > "Jalisco" > "45900")running henry in ghci results in:
Henry Herman Laxen
Henry Laxen
home: Via Alta #6
Chapala Jalisco 45900
Define upper convert a string to upper case, just so we have a nice function of type (a -> a) lying around
> upper :: String -> String > upper = Prelude.map toUpperNow lets see what these things do:
> test1 = address ^$ henry
*Main> test1 home: Via Alta #6 Chapala Jalisco 45900
> test2 = henry ^. address
*Main> test2
home: Via Alta #6
Chapala Jalisco 45900
> test3 = street ^$ address ^$ henry
*Main> test3 "Via Alta #6"
> test4 = (henry ^. address) ^. street
*Main> test4 "Via Alta #6"whereas
> -- test4a = henry ^. address ^. streetresults in
Couldn't match expected type `Data.Lens.Common.Lens Person Address'
with actual type `Address'
Expected type: Data.Lens.Common.Lens
(Data.Lens.Common.Lens Person Address)
(Data.Lens.Common.Lens Person b0)
Actual type: Data.Lens.Common.Lens Address String
In the second argument of `(^.)', namely `street'
In the second argument of `(^.)', namely `address ^. street'
so if we want to drill down to a specific field, we had better use the
(^$) version of the getter
That's all for getters, now let's look at setters and modifiers.
> test5 = fullName ^= "Henry H. Laxen" $ henry
*Main> test5
Henry H. Laxen
Henry Laxen
home: Via Alta #6
Chapala Jalisco 45900
> test6 = fullName ^%= upper $ henry
*Main> test6
HENRY HERMAN LAXEN
Henry Laxen
home: Via Alta #6
Chapala Jalisco 45900
> test7 = fullName ^%= upper $ test5
*Main> test7
HENRY H. LAXEN
Henry Laxen
home: Via Alta #6
Chapala Jalisco 45900
To do more interesting things, I always need my wife.
> nadine = Person > "Nadine Callaway Laxen" > "Nadine" > "Laxen" > (address ^$ henry)Another way of creating nadine, since she lives at the same address, and has the same last name.
> nadineAgain :: Person > nadineAgain = (fullName ^= "Nadine Callaway Laxen") . > (familiarName ^= "Nadine") $ henryA silly example of using fmap.
> addMister :: String -> Maybe String > addMister x = Just ( "Mr. " ++ x )
> mister :: Maybe Person > mister = fullName ^%%= addMister $ henry
*Main> mister
Just
Mr. Henry Herman Laxen
Henry Laxen
home: Via Alta #6
Chapala Jalisco 45900
There is also a Lenses way of manipulating Maps
> type ByFamiliarName = Map String Person
> justHenry :: ByFamiliarName > justHenry = fromList [("Henry",henry)] > addNadine :: ByFamiliarName -> ByFamiliarName > addNadine = mapLens "Nadine" ^= Just nadine > removeHenry :: ByFamiliarName -> ByFamiliarName > removeHenry = mapLens "Henry" ^= Nothing
> test8 = addNadine justHenry
*Main> test8
fromList [("Henry",
Henry Herman Laxen
Henry Laxen
home: Via Alta #6
Chapala Jalisco 45900
),("Nadine",
Nadine Callaway Laxen
Nadine Laxen
home: Via Alta #6
Chapala Jalisco 45900
)]
> test9 = removeHenry (addNadine justHenry)
*Main> test9
fromList [("Nadine",
Nadine Callaway Laxen
Nadine Laxen
home: Via Alta #6
Chapala Jalisco 45900
)]
I also wanted to try out the Stateful operators
> changeFamiliarWithState :: State Person String > changeFamiliarWithState = do > x <- access familiarName > if x == "Nadine" then familiarName ~= "Sweetie" > else familiarName %= upper > return x
> test10 = runState changeFamiliarWithState henry
*Main> test10
("Henry",
Henry Herman Laxen
HENRY Laxen
home: Via Alta #6
Chapala Jalisco 45900
)
You'll notice that runState :: State s a -> s -> (a, s) so that the
first part of the tuple returned is "Henry", because of the return
x, and the second part is the updated state, which in this case
since the familiarName wasn't Nadine is changed to upper case.
> test11 = runState changeFamiliarWithState nadine
*Main> test11
("Nadine",
Nadine Callaway Laxen
Sweetie Laxen
home: Via Alta #6
Chapala Jalisco 45900
)
In this test, since the familiar name was Nadine, we change it to
Sweetie in the state, and return it unchanged as the result.
So I would say, there are just a few things you should have at your fingertips when using lenses, namely:
drill ^$ down ^$ to ^$ theData
Lens Lens Lens Top Level Data
to set something:
(field ^= newValue) :: DataType -> DataType
Lens value
to modify something:
(field ^%= function) :: DataType -> DataType
Lens function :: (value -> value)
The rest you can look up and figure out.
Sitemap
Go up to Haskell Go up to Home Page of Nadine Loves Henry
Go back to A look at Heist, MVars and Anansi Continue with Nadine and Henry's Calcudoku Solver