1

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`
Josh.F
  • 3,666
  • 2
  • 27
  • 37
  • 1
    @josh-f u can use `Fold` like `num4 = text ^.. values . runFold ((,) <$> Fold (key "a" . _Integer) <*> Fold (key "b" . _Integer))` – xgrommx Jul 27 '22 at 08:40
  • 1
    unfortunately I can't add this as an answer because this is flagged as a duplicate, but while answers to the linked question are correct that such a function would be unsound, I'm fairly sure the "correct unsound" function to use is `lensProduct` https://hackage.haskell.org/package/lens-5.1.1/docs/Control-Lens-Unsound.html#v:lensProduct – Joe Jul 27 '22 at 10:08

0 Answers0