TL;DR Lenses are confusing and here I track an effort to compose lenses "horizontally". There's 1 solution using to, and questions on why Getter and alongside don't work.
Lenses compose nicely in a "vertical" sense using (.), which allows you to stack lenses that drill into an object.
But what if in 1 traversal of on object, you'd like 2 lenses to act on the same leaf nodes, and extract a product, say, a tuple?
For example, you have some JSON:
[ {"a":1, "b":11},
{"a":2, "b":22},
{"a":3, "b":33}]
And you'd like to write a function to extract values at "a" and "b" into tuples:
ex :: [(Int, Int)]
ex = [(1, 11), (2, 22), (3, 33)]
One Solution
After great lengths, I found I could do this using to, and all the following permutations are equivalent (I think):
text :: Text
text = "[{\"a\":1, \"b\":11}, {\"a\":2, \"b\":22}, {\"a\":3, \"b\":33}]"
num0, num1, num2, num3 :: [(Maybe Integer, Maybe Integer)]
num0 = text ^.. _Array . each . to (\o -> (o ^? key "a" . _Integer, o ^? key "b" . _Integer))
num1 = text ^.. values . to (\o -> (o ^? key "a" . _Integer, o ^? key "b" . _Integer))
num2 = text ^.. _Array . traverse . to (\o -> (o ^? key "a" . _Integer, o ^? key "b" . _Integer))
num3 = text ^.. _Array . traversed . to (\o -> (o ^? key "a" . _Integer, o ^? key "b" . _Integer))
(Note if you try for [(Int, Int)] instead of the above Maybe version, you'll get some confusing error messages about There's no monoid instance for Integer. That makes sense.)
This answer is disappointing since it requires to over a lambda that extracts lenses separately, and packages them into a tuple separately. It'd be nice if some combinators allowed me to compose this directly.
Why doesn't Getter work?
Based on examples and documentation, Getter sounds like exactly what I want:
num4 :: [(Integer, Integer)]
num4 = text ^.. values
. runGetter ((,)
<$> Getter (key "a" . _Integer)
<*> Getter (key "b" . _Integer))
But alas, I get an error message:
Could not deduce (Applicative f) arising from a use of 'key'
The f in question is existentially quantified (or... is it, since it's a type synonym), so, does that make it
incompatible with 'Getter'? Or...?
Why doesn't alongside work?
alongside also sounds like it should work, but again, no luck.
num5 :: [(Integer, Integer)]
num5 = text ^.. values . alongside (key "a" . _Integer) (key "b" . _Integer)
I couldn't figure that error out either
Couldn't match type `(t0, t'0)` with `Value`