How can I use lenses to obtain keys from multiple levels of nesting?
Consider the following types
data Outer = Outer { _outerMap :: Map String Inner }
data Inner = Inner { _innerMap :: Map Char Int }
makeLenses ''Outer
makeLenses ''Inner
and assume the following example value
example :: Outer
example = Outer $ Map.fromList
[ ("A", Inner $ Map.fromList [ ('a', 1), ('b', 2), ('c', 3) ])
, ("B", Inner $ Map.fromList [ ('a', 4), ('b', 6), ('c', 8) ])
, ("C", Inner $ Map.fromList [ ('a', 5), ('b', 7), ('c', 9) ])
]
Using lenses I can flatten example
to a [Int]
and filter the odd numbers as follows:
>>> example^..outerMap.folded.innerMap.folded.filtered odd
[1,3,5,7,9]
I can annotate the values with the inner key as follows:
>>> example^@..outerMap.folded.innerMap.ifolded.filtered odd
[('a',1),('c',3),('a',5),('b',7),('c',9)]
But how can I use lenses to annotate the values with both the outer and inner keys, to get the following result?
>>> _whatHere example
[(("A",'a'),1),(("A",'c'),3),(("C",'a'),5),(("C",'b'),7),(("C",'c'),9)]
The following attempt still only returns the inner keys:
>>> example^@..outerMap.ifolded.innerMap.ifolded.filtered odd
[('a',1),('c',3),('a',5),('b',7),('c',9)]
And the following attempt doesn't type-check
>>> example^..outerMap.ifolded.withIndex.alongside id (innerMap.ifolded.filtered odd.withIndex)
error:
• No instance for (Applicative
(Control.Lens.Internal.Getter.AlongsideRight
(Const (Data.Monoid.Endo [([Char], (Char, Int))])) [Char]))
An implementation without lenses might look something like this:
nolens :: Outer -> [((String, Char), Int)]
nolens =
filter (odd . snd)
. foldMap (\(k, i) -> (map (first (k, )) . Map.toList . _innerMap) i)
. Map.toList
. _outerMap
Use (<.>)
. It's just like (.)
, except it preserves the indices on both the left and the right. (.)
itself (and its alias (.>)
) preserves only the index of the RHS, unless the RHS is itself index-preserving, in which case the index comes from the LHS. The mnemonic is that the arrows point to the indices you'd like to save.
>>> example^@..outerMap.ifolded<.>innerMap.ifolded.filtered odd
[(("A",'a'),1),(("A",'c'),3),(("C",'a'),5),(("C",'b'),7),(("C",'c'),9)]