STM containers does it right.
lookup :: k -> Map K v -> STM (Maybe v)
insert :: v -> k -> Map K v -> STM ()
It won't execute until you wrap it with atomically, so you're forced to be explicit about whether these things should happen in one logical step or separately.https://hackage-content.haskell.org/package/stm-containers/d...
There is also the Clojure-style immutable API for maps though I can say I never really enjoyed working with that the way I enjoyed immutable lists (which are Lispier than Lisp: I went through all the stages of grief reading On Lisp and couldn't help think "If he was using Clojure he wouldn't be struggling with nconc)