Search code examples
haskelllenseshaskell-lens

Using Lens in Haskell to modify values


I find myself using this pattern often:

do
    let oldHeaders = mail ^. headers
    put $ (headers .~ (insert header value oldHeaders)) mail

which seems like the kind of thing Control.Lens should be able to do, but I guess I just haven't found the right operator yet. Is there a better way? Also, is there anything else I should be doing differently in this code?


Solution

  • You can use a chain of Lenses and Traversals to access the inner header value directly and update it.

    put $ mail & headers . at header ?~ value
    

    Note that (?~) is just shorthand for \lens value -> lens .~ Just value. The Just is needed to indicate to the at lens that we want to insert a value if it doesn't exist already.

    If mail in the first line comes from the state monad like this

    do
      mail <- get
      let oldHeaders = mail ^. headers
      put $ (headers .~ (insert header value oldHeaders)) mail
    

    then it's simpler to write that with modify :: MonadState s m => (s -> s) -> m ()

    modify (headers . at header ?~ value)
    

    Which, as suggested by Ørjan Johansen in the comments, can be written most pithily as

    headers . at header ?= value