3
votes

Control.Lens.Tuple defines lenses to access elements of tuples. For example

class Field3 s t a b | s -> a, t -> b, s b -> t, t a -> s where
  -- | Access the 3rd field of a tuple.
  _3 :: Lens s t a b

  -- >>> (1,2,3)^._3
  -- 3
  --
  -- >>> _3 .~ "hello" $ (1,2,3)
  -- (1,2,"hello")

The setter _3 .~ returns a tuple with the third element changed, to a different type in this example.

For class Field3 there's instances defined for tuples with three or more elements. Not for pairs/two-ples, because what would a getter get?

But if a setter can change the type of the nth element, why couldn't it change the type to a different arity of tuple?

  -- >>> _3 .~ "hello" $ (1,2)
  -- (1,2,"hello")

That is, a setter _3 .~ setting the third element by extending a two-ple.

instance Field3 (a,b) (a,b,c') Dummy c' where
  _3 k ~(a,b) = k undefined <&> \c' -> (a,b,c')

In which Dummy and undefined is some place-holder type and value for the non-existent third element of the incoming two-ple.

After extending to arity 3, lens _3 works as usual; also lenses _1, _2 obey the lens laws throughout.

Q1. Will this work for a setter?
(of course it won't for an updater/over)

Q2. If so, is there some way to make it ill-typed to try viewing the non-existent 3rd element?

Q3. If a setter won't work, could there be an extend operator that will achieve the effect? (Presumably using a different functor.)

Perhaps the instance could be this

instance Field3 (a,b) (a,b,c') (a,b) c' where
  _3 k ~(a,b) = k (a,b) <&> \c' -> (a,b,c')

Such that view would return a weirdly typed result, and over could potentially build the third element from the first two.

1
I kinda got this working, in the sense _3 .~ "hello" $ (1,2) was accepted and produced the result I wanted. But the instance wasn't accepted: inconsistent with the proper instance, for FunDep t a -> s -- which is quite correct. I just suppressed that FunDep on the class. - AntC
If all you want is a setter (a, b) -> c -> (a, b, c), what is the rest of the lens infrastructure getting you that _3' = uncurry (,,) doesn't? Genericity of some sort? - user11228628
My two-ple is of course deeply nested inside all sorts of other structures I'm lensing into. So I want something I can compose as a lens. Then your _3' is not that. I see no Functor. - AntC
It's a setter. It doesn't need to operate over an arbitrary Functor; its Functor is Identity. If you have a van Laarhoven lens forall f. Functor f => ((a, b) -> f (a, b, c)) -> s -> f t, you should be able to compose that with c -> (a, b) -> Identity (a, b, c) to get a setter in the style you want for your larger structure. That's the beauty of van Laarhoven lenses; you can do an awful lot with plain old composition. - user11228628
Sorry @user11228628, I'm not seeing it. Using my def'n set _3 "hello" (1, 2) gives (1,2,"hello"), and the type of _3 has a Functor required in its constraints. Using your def'n set _3' "hello" (1,2) is ill-typed, and the type of _3' doesn't mention Functor. set would introduce the Identity Functor. - AntC

1 Answers

2
votes

In the van Laarhoven lens formulation, a optic is just a higher-order function that takes an action on a smaller structure to an action on a larger structure. These functions can be composed, and then used with the sugar provided in the lens package to make calling them mindless. But they can also just be called.

Let's say you have this:

bigStructure :: (Int, (Int, (Int, Int)))
bigStructure = (0, (1, (2, 3)))

And you want to append a 10 to the innermost pair. That's an action on that pair, which is a smaller piece of the big structure.

mk3ple :: c -> (a, b) -> (a, b, c)
mk3ple = flip $ uncurry (,,)

So _2 . _2 is a lens focusing on that innermost pair—in other words, it's a function that takes actions on that innermost pair to actions on the big structure. So let's just... pass mk3ple to it (over does this; it just takes care of some Identity busywork for us):

over (_2 . _2) (mk3ple 10) bigStructure

And there you are—a setter (conceptual, that is; not a literal Setter) for bigStructure that uses whatever lenses you need to focus on the tuple.


Of course, if you'd rather have the tuple-appender be an actual lens with a getter and everything, you can certainly do that too! Just think of unit as a part of every tuple, and write:

mk3ple' :: Lens (a, b) (a, b, c) () c
mk3ple' = lens (const ()) (uncurry (,,))

set (_2 . _2 . mk3ple') 10 bigStructure