Search code examples
haskellmonadslenses

How can I lift an fclabels Lens to a Monad?


I'm trying to update some old code using fclabels, from v0.4 to v2.0 (lts-3.17), that lifts a label/lens to a monad. The old code is:

{-# LANGUAGE TypeOperators #-}
import Data.Record.Label
import Control.Monad (liftM, liftM2)

liftMLabel :: Monad m => a :-> b -> m a :-> m b
liftMLabel l = label (liftM $ get l) (liftM2 $ set l)

So the first thing I did was change label to lens, and set to modify:

{-# LANGUAGE TypeOperators #-}
import Data.Label
import Control.Monad (liftM, liftM2)

liftMLabel :: Monad m => a :-> b -> m a :-> m b
liftMLens l = lens (liftM $ get l) (liftM2 $ modify l)

This gave the following compiler error:

Expected type: (m b -> m b) -> m a -> m a
  Actual type: m (b -> b) -> m a -> m a

Which makes sense, giving how liftM2 would treat each arg of the lens modify function.

The old fclabels used a setter function for creating a label, which accepts a simple value argument. The modify function, used for creating newer fclabels lenses, takes a function for modifying using the existing value, and I see why it too will operate on monadic arguments.

I'll need to perform some extra plumbing for the modify function, and I see that ap does something similar to what I want, but it's not clear to me what the best approach is in totality.

So what is a good way to deal with the modify function, so I can match the expected type?


Solution

  • Instead of trying to write it all in one go in point-free style, why not just write it in long-hand?

    liftMLens :: (Monad m) => a :-> b -> m a :-> m b
    liftMLens l = lens (liftM $ get l) $ \f mx -> do
        x <- mx
        let v = get l x
        v' <- f $ return v
        return $ set l v' x
    

    Of course you can then code-golf it if you really want to...