4

Python's enumerate on lists can be written as zip [0..]. I looked at Control.Lens.Traversal and Control.Lens.Indexed, but I couldn't figure out how to use lenses to generalize this to any reasonable container (I hesitate to say "Traversable").

I'm guessing itraverse or itraverseOf is key.

Alex R
  • 2,201
  • 1
  • 19
  • 32
  • I believe `lens` offers an indexed map, but I'm not sure. I know that if you're using `Data.Sequence`, it's far more efficient to use `mapWithIndex (,)` than any sort of traversal. – dfeuer Mar 18 '15 at 15:16
  • 1
    `itraverse` certainly does what you want. What difficulty are you having using it? – Carl Mar 18 '15 at 15:18
  • @Carl My naive intuition of `itraverse (,) "abcd"` complains about a lack of a monoid instance for Int. It also looks like using `(,)` as the first argument gives me the wrong output type, namely `(Int, t a)` rather than `t (Int, a)`. – Alex R Mar 18 '15 at 15:34
  • 2
    @AlexR Well, yes. `traverse` and `itraverse` work in an `Applicative` context, which you're ignoring. Try something like `imap f = runIdentity . itraverse (\i a -> return (f i a))`. – Carl Mar 18 '15 at 15:39
  • 1
    Dunno about lens, but this is fundamentally the same problem as discussed in ["How to decorate a Tree in Haskell"](http://stackoverflow.com/questions/12658443/how-to-decorate-a-tree-in-haskell/12658639). [The answer by hammar](http://stackoverflow.com/a/12662925/1094403) is the same as [bhelkir's response to your question](http://stackoverflow.com/a/29125450/1094403). – Luis Casillas Mar 19 '15 at 01:35

3 Answers3

8

If you're using a container that is an instance of FunctorWithIndex then you can simply use imap (,):

> imap (,) "abc"
[(0,'a'),(1,'b'),(2,'c')]

But if the index isn't the position this won't work:

> let m = Map.fromList [('a', "foo"), ('b', "bar"), ('c', "foobar")])
> imap (,) m
fromList [('a',('a',"foo")),('b',('b',"bar")),('c',('c',"foobar"))]

Instead you can use traversed, which is an indexed traversal where the index is the order the elements appear. This can be used for anything that's Traversable. Instead of imap use iover traversed (which is the same as imapOf but that's been deprecated):

> iover traversed (,) "abc"
[(0,'a'),(1,'b'),(2,'c')]

> iover traversed (,) m
fromList [('a',(0,"foo")),('b',(1,"bar")),('c',(2,"foobar"))]
cchalmers
  • 2,896
  • 1
  • 11
  • 11
6

One solution would be to use the State monad with traverse, since it is also Applicative:

enumerate :: (Integral n, Traversable t) => t a -> t (n, a)
enumerate t = evalState (traverse go t) 0
    where
        go a = do
            i <- get
            modify (+1)
            return (i, a)
bheklilr
  • 53,530
  • 6
  • 107
  • 163
  • 1
    In an `Applicative` style, by the way, `go a = (\i -> (i,a)) <$> get <* modify (+1)`. May I suggest `(Traversable t, Integral n) => t a -> t (n, a)`? – dfeuer Mar 18 '15 at 15:10
  • @dfeuer Sure, I was just being lazy. You could even relax it further to `Num` if you really wanted. – bheklilr Mar 18 '15 at 15:34
  • That's the solution I'd probably use normally (well, looking at my code, it more closely resembles @dfeuer modification, except using tuple sections and `succ`. I'm looking for a Lens-specific answer, though, in part to expand my understanding of the package. – Alex R Mar 18 '15 at 15:37
  • @AlexR, I avoided tuple sections just because they're not standard; I'd use them too. You're right that using `Enum` makes a lot of sense, but `Enum` is a terrible, horrible, no good, very bad class :-/ – dfeuer Mar 18 '15 at 15:41
3

You're ignoring the Applicative context on itraverse. You need something for it to be working with. But the something can be boring, like Identity.

imap f = runIdentity . itraverse (\i a -> return (f i a))

And then you get what you're looking for:

> imap (,) [1,2,3]
[(0,1),(1,2),(2,3)]
Carl
  • 26,500
  • 4
  • 65
  • 86