4

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.

pat
  • 12,587
  • 1
  • 23
  • 52

1 Answers1

6

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.

dfeuer
  • 48,079
  • 5
  • 63
  • 167