In Control.Lens.Lens
, there is a function
modifying :: MonadState s m => ASetter s s a b -> (a -> b) -> m ()
which allows the value under a lens on the MonadState
state to be transformed by a pure function (a -> b)
.
However, we may want to allow the transform function to fail in m
, requiring it to have type (a -> m b)
.
I've looked through the lens library for such a function, but I can't find one, so I implemented:
modifyingM l f = use l >>= f >>= assign l
Which does the trick, but I was wondering if there is a function already in the lens library that will do this.
I don't see anything like that. ASetter
is defined
type ASetter s t a b = (a -> Identity b) -> s -> Identity t
so it's not powerful enough for the job (and Setter
can't do it either). It turns out, on the other hand, that a Lens
is a bit stronger than necessary. Let's consider, then, how to do it with a Traversal
.
type Traversal s t a b =
forall f. Applicative f => (a -> f b) -> s -> f t
So
Traversal s s a b =
forall f. Applicative f => (a -> f b) -> s -> f s
Which Applicative
do we want? m
seems like the obvious one to try. When we pass the traversal a -> m b
, we get back s -> m s
. Great! As usual for lens
, we'll actually only require the user to provide an ATraversal
, which we can clone.
modifyingM
:: MonadState s m
=> ATraversal s s a b
-> (a -> m b) -> m ()
modifyingM t f = do
s <- get
s' <- cloneTraversal t f s
put s'
That's nice because it only traverses the state once.
Even that is overkill, really. The most natural thing is actually
modifyingM
:: MonadState s m
=> LensLike m s s a b
-> (a -> m b) -> m ()
modifyingM t f = do
s <- get
s' <- t f s
put s'
You can apply that directly to a Traversal
, Lens
, Iso
, or Equality
, or use cloneTraversal
, cloneLens
, cloneIso
, or (in lens-4.18
or later) cloneEquality
to apply it to the monomorphic variants.