Search code examples
haskellvectorhaskell-lens

Accessing vector element by index using lens


I'm looking for a way to reference an element of a vector using lens library...

Let me try to explain what I'm trying to achieve using a simplified example of my code.

I'm working in this monad transformer stack (where StateT is the focus, everything else is not important)

newtype MyType a = MyType (StateT MyState (ExceptT String IO) a)

MyState has a lot of fields but one of those is a vector of clients which is a data type I defined:

data MyState = MyState { ...
                       , _clients :: V.Vector ClientT
                       }

Whenever I need to access one of my clients I tend to do it like this:

import Control.Lens (use)

c <- use clients
let neededClient = c V.! someIndex
... -- calculate something, update client if needed
clients %= (V.// [(someIndex, updatedClient)])

Now, here is what I'm looking for: I would like my function to receive a "reference" to the client I'm interested in and use it (retrieve it from State, update it if needed).

In order to clear up what I mean here is a snippet (that won't compile even in pseudo code):

...
myFunction (clients.ix 0)
...

myFunction clientLens = do
    c <- use clientLens -- I would like to access a client in the vector
    ... -- calculate stuff
    clientLens .= updatedClient

Basically, I would like to pass to myFunction something from Lens library (I don't know what I'm passing here... Lens? Traversal? Getting? some other thingy?) which will allow me to point at particular element in the vector which is kept in my StateT. Is it at all possible? Currently, when using "clients.ix 0" I get an error that my ClientT is not an instance of Monoid.

It is a very dumbed down version of what I have. In order to answer the question "why I need it this way" requires a lot more explanation. I'm interested if it is possible to pass this "reference" which will point to some element in my vector which is kept in State.


Solution

  • clients.ix 0 is a traversal. In particular, traversals are setters, so setting and modifying should work fine:

    clients.ix 0 .= updatedClient
    

    Your problem is with use. Because a traversal doesn't necessarily contain exactly one value, when you use a traversal (or use some other getter function on it), it combines all the values assuming they are of a Monoid type.

    In particular,

    use (clients.ix n)
    

    would want to return mempty if n is out of bounds.

    Instead, you can use the preuse function, which discards all but the first target of a traversal (or more generally, a fold), and wraps it in a Maybe. E.g.

    Just c <- preuse (clients.ix n)
    

    Note this will give a pattern match error if n is out of bounds, since preuse returns Nothing then.