Let's say I have some fairly simple data type Person
with a couple of fields, and a type that holds a collection of Person
s.
data Person = Person { _name :: String, _age :: Int }
data ProgramState = PS { _dict :: IntMap Person }
makeLenses ''Person
makeLenses ''ProgramState
I want to create a lens that allows me to access individual people by looking up their key
person :: Int -> Lens' ProgramState Person
It seems my two options for doing this are to use at
or ix
to index into the dictionary
-- Option 1, using 'at'
person :: Int -> Lens' ProgramState (Maybe Person)
person key = dict . at key
-- Option 2, using 'ix'
person :: Int -> Traversal' ProgramState Person
person key = dict . ix key
but neither of these options lets me do what I want, which is to have a Lens'
that accesses a Person
rather than a Maybe Person
. Option 1 doesn't compose nicely with other lenses, and option 2 means that I have to give up my getters.
I understand why ix
and at
are written like this. The key might not exist in the dict, so if you want a Lens'
which enables both getters and setters, it must access a Maybe a
. The alternative is to accept a Traversal'
which gives access to 0 or 1 values, but that means giving up your getters. But in my case, I know that the element I want will always be present, so I don't need to worry about missing keys.
Is there a way to write what I want to write - or should I be rethinking the structure of my program?
You probably want to use at
together with the non
isomorphism. You can specify a default map entry with it to get rid of the Maybe
of the lookup.
non :: Eq a => a -> Iso' (Maybe a) a
person key = dict . at key . non defaultEntry
-- can get and set just like plain lenses
someProgramState & dict . at someKey . non defaultEntry .~ somePerson
You can look at more examples in the docs.