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`