Arrows have two kinds of composition, vertical:
(.) :: Arrow cat => cat b c -> cat a b -> cat a c
and horizontal:
(***) :: Arrow cat => cat a b -> cat a' b' -> cat (a,a') (b,b')
(I apologize to any category theoryists for any abuse of terminology)
Lenses can also compose vertically:
(.) :: Lens s t q r -> Lens q r a b -> Lens s t a b
Can they compose horizontally?
Using the normal definition of van Laarhoven lenses and concrete lenses:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
type Lens' s a = Lens s s a a
data ALens s t a b = ALens
{ get :: s -> a
, set :: b -> s -> t
}
type ALens' s a = ALens s s a a
fromALens :: ALens s t a b -> Lens s t a b
toALens :: Lens s t a b -> ALens s t a b
I figured out how to horizontally compose simple concrete lenses:
along :: ALens' s a -> ALens' s b -> ALens' s (a,b)
along l0 l1 = ALens
{ get = \s -> (get l0 s, get l1 s)
, set = \(a,c) -> set l1 c . set l0 a
}
And how to use that definition to horizontally compose simple van Laarhoven lenses:
zip :: Lens' s a -> Lens' s b -> Lens' s (a,b)
zip l0 l1 = fromALens (toALens l0 `along` toALens l1)
Without changing the implementation, I can even relax the type signatures of
along
and zip
to allow one of the lenses to be polymorphic:
along :: ALens' s a -> ALens s t b c -> ALens s t (a,b) (a,c)
zip :: Lens' s a -> Lens s t b c -> Lens s t (a,b) (a,c)
And after some manual equational reasoning, I managed to implement zip
without
conversion to concrete lenses and back:
zip l0 l1 h s = h (s ^. l0, s ^. l1) <&> \(a,c) -> s & (l0 .~ a) & (l1 .~ c)
(though I'm certainly not confident that it's the most elegant or law-abiding implementation)
What I'm still wondering is whether there's an equivalent horizontal composition for non-simple lenses:
(***) :: Lens r s a b -> Lens s t c d -> Lens r t (a,a') (b,b')