haskellmonadshaskell-lens

Can I make a Lens with a Monad constraint?


Context: This question is specifically in reference to Control.Lens (version 3.9.1 at the time of this writing)

I've been using the lens library and it is very nice to be able to read and write to a piece (or pieces for traversals) of a structure. I then had a though about whether a lens could be used against an external database. Of course, I would then need to execute in the IO Monad. So to generalize:

Question:

Given a getter, (s -> m a) and an setter (b -> s -> m t) where m is a Monad, is possible to construct Lens s t a b where the Functor of the lens is now contained to also be a Monad? Would it still be possible to compose these with (.) with other "purely functional" lenses?

Example:

Could I make Lens (MVar a) (MVar b) a b using readMVar and withMVar?

Alternative:

Is there an equivalent to Control.Lens for containers in the IO monad such as MVar or IORef (or STDIN)?


Solution

  • I've been thinking about this idea for some time, which I'd call mutable lenses. So far, I haven't made it into a package, let me know, if you'd benefit from it.

    First let's recall the generalized van Laarhoven Lenses (after some imports we'll need later):

    {-# LANGUAGE RankNTypes #-}
    import qualified Data.ByteString as BS
    import           Data.Functor.Constant
    import           Data.Functor.Identity
    import           Data.Traversable (Traversable)
    import qualified Data.Traversable as T
    import           Control.Monad
    import           Control.Monad.STM
    import           Control.Concurrent.STM.TVar
    
    type Lens s t a b = forall f . (Functor f) => (a -> f b) -> (s -> f t)
    type Lens' s a = Lens s s a a
    

    we can create such a lens from a "getter" and a "setter" as

    mkLens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
    mkLens g s  f x = fmap (s x) (f (g x))
    

    and get a "getter"/"setter" from a lens back as

    get :: Lens s t a b -> (s -> a)
    get l = getConstant . l Constant
    
    set :: Lens s t a b -> (s -> b -> t)
    set l x v = runIdentity $ l (const $ Identity v) x
    

    as an example, the following lens accesses the first element of a pair:

    _1 :: Lens' (a, b) a
    _1 = mkLens fst (\(x, y) x' -> (x', y))
    -- or directly: _1 f (a,c) = (\b -> (b,c)) `fmap` f a
    

    Now how a mutable lens should work? Getting some container's content involves a monadic action. And setting a value doesn't change the container, it remains the same, just as a mutable piece of memory does. So the result of a mutable lens will have to be monadic, and instead of the return type container t we'll have just (). Moreover, the Functor constraint isn't enough, since we need to interleave it with monadic computations. Therefore, we'll need Traversable:

    type MutableLensM  m s  a b
        = forall f . (Traversable f) => (a -> f b) -> (s -> m (f ()))
    type MutableLensM' m s  a
        = MutableLensM m s a a
    

    (Traversable is to monadic computations what Functor is to pure computations).

    Again, we create helper functions

    mkLensM :: (Monad m) => (s -> m a) -> (s -> b -> m ())
            -> MutableLensM m s a b
    mkLensM g s  f x = g x >>= T.mapM (s x) . f
    
    
    mget :: (Monad m) => MutableLensM m s a b -> s -> m a
    mget l s = liftM getConstant $ l Constant s
    
    mset :: (Monad m) => MutableLensM m s a b -> s -> b -> m ()
    mset l s v = liftM runIdentity $ l (const $ Identity v) s
    

    As an example, let's create a mutable lens from a TVar within STM:

    alterTVar :: MutableLensM' STM (TVar a) a
    alterTVar = mkLensM readTVar writeTVar
    

    These lenses are one-sidedly directly composable with Lens, for example

    alterTVar . _1 :: MutableLensM' STM (TVar (a, b)) a
    

    Notes: