Search code examples
haskellhaskell-lens

Confusion about composing indexed lens


I can create an IndexedTraversal for lists with a constant index like so:

constIndexedList :: i -> Lens.AnIndexedTraversal i [a] [b] a b
constIndexedList _ _ [] = pure []
constIndexedList index f (x:xs) = (:) <$> Lens.indexed f index x <*> constListIndex index f xs

I'd rather create it by composing two simpler lens:

constIndex :: i -> Lens.AnIndexedLens i a b a b
constIndex index f = Lens.indexed f index

constIndexedList :: i -> Lens.AnIndexedTraversal i [a] [b] a b
constIndexedList index = Lens.cloneLens (constIndex index) <. traverse

However this fails to type-check, and the type errors from lens are not ones that I easily understand:

• Couldn't match type ‘Lens.Indexed
                         i (t0 a) (Lens.Bazaar (Lens.Indexed i) a b (t0 b))’
                 with ‘[a] -> Lens.Bazaar (Lens.Indexed i) a b [b]’
  Expected type: Lens.Indexed
                   i (t0 a) (Lens.Bazaar (Lens.Indexed i) a b (t0 b))
                 -> [a] -> Lens.Bazaar (Lens.Indexed i) a b [b]
    Actual type: ([a] -> Lens.Bazaar (Lens.Indexed i) a b [b])
                 -> [a] -> Lens.Bazaar (Lens.Indexed i) a b [b]
• In the first argument of ‘(<.)’, namely
    ‘Lens.cloneLens (constIndex index)’
  In the expression: Lens.cloneLens (constIndex index) <. traverse

Why can't I compose indexed lens like so?

I ended up finding a different modular way to do it (Lens.reindexed (const index) Lens.traversed, but I still wonder why the method mentioned above doesn't work..


Solution

  • (<.) expects an indexed optic on its left, but cloneLens gives it an unindexed lens.

    One partial fix is to use Lens.cloneIndexedLens instead of cloneLens.

    However, A_/An_ variants of optics (AnIndexedLens) are meant to be argument types, not result types, which can use regular optics (IndexedLens). With that, cloneIndexedLens becomes unnecessary.

    constIndex :: i -> Lens.IndexedLens i a b a b
    constIndex i f = Lens.indexed f i
    
    constIndexedList :: i -> Lens.IndexedTraversal i [a] [b] a b
    constIndexedList i = constIndex i <. traverse
    

    Monomorphic inputs and polymorphic outputs improve composability.

    IndexedLens is a polymorphic type (notice the forall keyword in the type definition).

    type IndexedLens i s t a b = forall f p. (Indexable i p, Functor f) => p a (f b) -> s -> f t
    

    AnIndexedLens is monomorphic.

    type AnIndexedLens i s t a b = Optical (Indexed i) (->) (Pretext (Indexed i) a b) s t a b
    

    Every IndexedLens is AnIndexedLens, but the conversion the other way must go through an explicit cloneIndexedLens.