0

A traversable may be labelled. To take this idea one step further, one may apply a function to any element of a traversable by its index.

import Control.Monad.State

updateAt :: forall a. Int -> (a -> a) -> [a] -> [a]
updateAt i f = flip evalState [0..] . traverse g
  where
    g :: a -> State [Int] a
    g x = do
        js <- get
        case js of
            [ ]      -> error ""
            (j: js') -> do
                put js'
                if j == i
                   then return (f x)
                   else return x

In Haskell, there is an attempt to generalize or otherwise sort out operations like this. First it was keys, then it grew up into lens. It is now a huge package. I am trying to make sense of it.

To this end, I am trying to do simple things first. One simple thing is what I started with — to label a traversable. Can it be done? Further, can it be done on a "low level"?

element seems to be doing the same as my example above, so I checked its definition. It led me to Indexable, then to this:

class ( Choice p, Corepresentable p, Comonad (Corep p)
      , Traversable (Corep p), Strong p, Representable p, Monad (Rep p)
      , MonadFix (Rep p), Distributive (Rep p), Costrong p, ArrowLoop p
      , ArrowApply p, ArrowChoice p, Closed p
      ) => Conjoined p

I admit this is a bit over my head. I like how "indexable" sounds — it must be useful. But it also seems to be the hardest piece of Haskell ever written. I understand that Conjoined is an advanced kind of a profunctor, so basically... a function? I am not sure what it might be, and how this all connects to keyed containers.

Is it applicable to my problem? What is it for? How can I make sense of it?

Ignat Insarov
  • 4,660
  • 18
  • 37

1 Answers1

2

Indexable i p really just means "p is either (->) or Indexed i (Indexed i a b = i -> a -> b)". The lens package is built on a tower of very abstract classes that makes everything very general. Specifically, instead of working with functions, it tries to work with general profunctors, but trying to deal with indices basically causes the whole thing to collapse down (very noisily, as you've seen) to just "the profunctor is either (->) or Indexed i".

In any case, you don't care about Indexable. The "index" you're talking about is the argument to element. The "index" in IndexedTraversable is a "result", each element returned by an IndexedTraversable also has its index associated with it. Here, it just returns the argument you passed in again, in case something else wants to get it. You don't. To recover updateAt, simply pass element's return value to over, specializing p to (->) and throwing away the duplicated index:

updateAt :: Traversable t => Int -> (a -> a) -> t a -> t a
updateAt = over . element
-- updateAt i f = over (element i) f
-- "over element i do f"

I'd say over is pretty "low-level"

-- basically
over :: ((a -> Identity b) -> (s -> Identity t)) -> (a -> b) -> (s -> t)
over setter f = runIdentity . setter (Identity . f)
-- I think even over = coerce would be valid
-- meaning it's actually just an identity function
-- and that updateAt = element (but with the type changed)

In general, I suppose the "portal" to "operations on Traversable with indices" is traversed, which basically "is" traverse (when you specialize its p to (->)). elements = elementsOf traverse = elementsOf traversed and element = elementOf traverse = elementsOf traversed just filter for specific indices.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • `over` is as high level as it gets. What I meant by _"low level"_ is something like [this](https://hackage.haskell.org/package/lens-4.18.1/docs/Control-Lens-Internal-Indexed.html#t:Indexing): >>> "cat" ^@.. indexing (folded <> folded) [(0,'c'),(1,'a'),(2,'t'),(3,'c'),(4,'a'),(5,'t')] When I look at `folded` and `traversed`, it is `indexed` and `conjoined` all over again: folded = conjoined (foldring foldr) (ifoldring ifoldr) traversed = conjoined traverse (indexing traverse) And I am still not grasping what it means. Maybe another question is in order? – Ignat Insarov Nov 12 '19 at 10:28
  • @IgnatInsarov it doesn't *mean* anything. It's an implementation detail, not a denotation. `conjoined` is roughly "here's the totally separate implementations for when using this traversal indexed or non-indexed, with all the necessary machinery to make GHC inline the correct version when it has enough information to determine which that is." – Carl Nov 14 '19 at 02:39