Given the types
data Prisoner = P { _name :: String
, _rank :: Int
, _cereal :: Cereal }
data Cereal = C { _number :: Int
, _percentDailyValue :: Map String Float
, _mascot :: String }
I could extract someone's name, rank, and cereal number via pattern matching:
getNameRankAndCerealNumber_0 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_0 (P { _name=name
, _rank=rank
, _cereal = C { _number=cerealNumber }}
) = (name, rank, cerealNumber)
Alternately, I could use lenses to extract each part separately
makeLenses ''Cereal
makeLenses ''Prisoner
getNameRankAndCerealNumber_1 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_1 p = (p ^. name, p ^. rank, p ^. cereal.number)
Is there a way to extract all three simultaneously in a single traversal of the data structure?
Some way to combine Getter
s, Getter s a -> Getter s b -> Getter s (a,b)
?
We can use the Applicative
instance of the ReifiedGetter
newtype from Control.Lens.Reified
:
runGetter $ (,) <$> Getter number <*> Getter mascot
In general, the newtypes in Control.Lens.Reified
offer a lot of very useful instances for getters and folds.
Note#1: Notice that we are combining the lenses as getters, and getting a getter in return. You can't obtain a composite lens in this way, as there would be problems if their "focuses" overlap. What could be the proper setter behaviour in that case?
Note#2: The alongside function lets you combine two lenses, getting a bona-fide lens that works on the two halves of a product. This is different form the previous case because we can be sure the lenses don't overlap. alongside
comes in handy when your type is a tuple or has an isomorphism to a tuple.
Fleshing out danidiaz's answer above, I was able to construct a Getter Prisoner (String, Int, Int)
using ReifiedGetter
:
getNameRankAndCerealNumber_2 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_2 = p ^. nameRankAndCerealNumber_2
nameRankAndCerealNumber_2 :: Getter Prisoner (String, Int, Int)
nameRankAndCerealNumber_2 = runGetter ((,,) <$> Getter name <*> Getter rank <*> Getter (cereal.number))
And a Lens' Prisoner (String, Int, Int)
using alongside
, though I had to manually construct Iso'
s between Prisoner
and an HList [String, Int, Int]
and between HList [a,b,c]
and (a,b,c)
.
getNameRankAndCerealNumber_3 :: Prisoner -> (String, Int, Int)
getNameRankAndCerealNumber_3 p = p ^. nameRankAndCerealNumber_3
setNameRankAndCerealNumber_3 :: (String, Int, Int) -> Prisoner -> Prisoner
setNameRankAndCerealNumber_3 t p = p & nameRankAndCerealNumber_3 .~ t
nameRankAndCerealNumber_3 :: Lens' Prisoner (String, Int, Int)
nameRankAndCerealNumber_3 = hlist . alongside id (alongside id number) . triple
where triple :: Iso' (a,(b,c)) (a,b,c)
triple = dimap (\(a,(b,c)) -> (a,b,c)) (fmap $ \(a,b,c) -> (a,(b,c)))
hlist :: Iso' Prisoner (String, (Int, Cereal))
hlist = dimap (\(P n r c) -> (n,(r,c))) (fmap $ \(n,(r,c)) -> P n r c)
There may be an easier way to do this, but that's another question.
来源:https://stackoverflow.com/questions/26722458/using-a-lens-to-read-multiple-fields