7

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?

Drew
  • 12,578
  • 11
  • 58
  • 98

2 Answers2

11

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
J. Abrahamson
  • 72,246
  • 9
  • 135
  • 180
  • Exactly what I was looking for! Now I just need to step through it so I make sure I understand it. – Drew Jan 13 '14 at 05:18
  • 4
    (Tried to make this an edit but was rejected:) Remembering that many of the pure setter operators ending in `~` have monadic state updating versions ending in `=`, we can combine the `?~` and the modify: `headers . at header ?= value` – Ørjan Johansen Jan 13 '14 at 06:08
  • 1
    Couldn't you just use `headers . at header . traversed .= value`? – Gabriella Gonzalez Jan 13 '14 at 06:57
  • 1
    No -- `at header . traversed .= value` and `at header ?= value` are different. The former (which can also be written as `ix header .= value`) can only change an existing value, and will do nothing if the value doesn't exist. – shachaf Jan 13 '14 at 09:04
  • @ØrjanJohansen I added your suggestion with a note. – J. Abrahamson Jan 13 '14 at 09:57
  • @GabrielGonzalez I've been bitten by the difference in semantics you bring up a few times. It's somewhat problematic that both typecheck. – J. Abrahamson Jan 13 '14 at 09:58
7

You don't usually need to get and put explicitly in the State monad when using lenses. In your case, you can use the operator ?= to update the state directly:

example = do
  headers . at header ?= value

You can also modify any lens with a function using %=:

example2 = do
  headers %= insert header value
shang
  • 24,642
  • 3
  • 58
  • 86