Search code examples
haskellhaskell-lens

Lens modifying with failure


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.


Solution

  • 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.