Can I make a Lens with a Monad constraint?

后端 未结 3 664
挽巷
挽巷 2020-12-31 08:26

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 le

3条回答
  •  一生所求
    2020-12-31 09:12

    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:

    • Mutable lenses could be made more powerful if we allow that the modifying function to include effects:

      type MutableLensM2  m s  a b
          = (Traversable f) => (a -> m (f b)) -> (s -> m (f ()))
      type MutableLensM2' m s  a
          = MutableLensM2 m s a a
      
      mkLensM2 :: (Monad m) => (s -> m a) -> (s -> b -> m ())
               -> MutableLensM2 m s a b
      mkLensM2 g s  f x = g x >>= f >>= T.mapM (s x)
      

      However, it has two major drawbacks:

      1. It isn't composable with pure Lens.
      2. Since the inner action is arbitrary, it allows you to shoot yourself in the foot by mutating this (or other) lens during the mutating operation itself.
    • There are other possibilities for monadic lenses. For example, we can create a monadic copy-on-write lens that preserves the original container (just as Lens does), but where the operation involves some monadic action:

      type LensCOW m s t a b
          = forall f . (Traversable f) => (a -> f b) -> (s -> m (f t))
      
    • I've made jLens - a Java library for mutable lenses, but the API is of course far from being as nice as Haskell lenses.

提交回复
热议问题