13

I have some datatypes along the line of

data Outer = Outer { _list :: [ Inner ] }
data Inner = Inner { _bool :: Bool }

using Control.Lens, I can access the _bool of the ith Inner (inside a 'State Outer' monad) like this

boolValue <- gets (^. list . to (!! i) . inner)

I would like to also be able to update this value with something like

list ^. (to (!! i)) ^. inner %= True

However (by my understanding), the 'to' function only creates a getter, not a true lens that can be used as either getter or setter.

So, how can I convert (!! i) into a lens that will allow me to update this field?

ajp
  • 1,723
  • 14
  • 22

2 Answers2

17

You can't* turn (!!) into any lens-like thing other than a Getter -- but there's a function to do this sort of thing: ix, for accessing things at indices. It's actually a Traversal, not a Lens -- which, here, just means that it can fail (if the index is out of bounds) -- but as long as the index is in the list, it'll work.

There's another problem, though -- (^.) is also an operator that's used exclusively for getting values. It's incompatible with e.g. (%=), which takes a lens-like thing as its first argument. And: (%=) is for mapping a function over the existing value; if you just want to set, you can use (.=). So you probably want something like:

list . ix i . inner .= True

* There actually is a function that can do this -- it's called upon -- but it uses wonderful evil black magic and you shouldn't use it, at least not for this (and probably not for any real code).

shachaf
  • 8,890
  • 1
  • 33
  • 51
  • could you clarify what the difference is between `ix` and `element`? I've gone with Gabriel's `element` since it seems simpler, taking an `Int` instead of an `Index`. From the docs you linked, it seems `ix` is more general in what it allows - is it strictly more general? – ajp Jun 09 '13 at 06:21
  • 1
    I didn't think of `element`. It isn't exactly that one is more general than the other... `ix` is a type class that has a bunch of instances for indexing with particular index types (for example, lists with `Int`, `Map`s with their index type, functions with their domain). `element` takes any `Traversable` type and counts left-to-right with an `Int` index. In this case they happen to coïncide. – shachaf Jun 09 '13 at 06:58
  • I haven't tested it, but I would guess that `ix` is a bit more efficient for this particular case (mostly because I haven't figured out a way to make `Indexing` generate good code... :-( Someone else is welcome to try). It's also a somewhat ad-hoc class. – shachaf Jun 09 '13 at 07:02
10

Use element, which is a Traversal to the specified list element:

list . element i . inner %= True :: Outer -> Outer

If you want to get the list element you must do so using a Maybe since the list element might not be there:

myList :: Outer

myList ^? list . element i . inner :: Maybe Bool
Gabriella Gonzalez
  • 34,863
  • 3
  • 77
  • 135