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