packages feed

ref-extras-0.2.0: src/RefExtras/LensRef.hs

module RefExtras.LensRef
  ( LensRef
  , mkLensRef
  , wholeLensRef
  , zoomLensRef
  , readLensRef
  , writeLensRef
  , modifyLensRef
  , atomicModifyLensRef
  ) where

import Lens.Micro (Lens', over, set)
import Lens.Micro.Extras (view)
import RefExtras.Classes (AtomicRef (..), ModifyRef (..), ReadWriteRef (..))

-- | A reference /focusing in/ on a part of a larger structure.
data LensRef r a where
  LensRef :: !(r z) -> !(Lens' z a) -> LensRef r a

-- | 'LensRef' constructor
mkLensRef :: r z -> Lens' z a -> LensRef r a
mkLensRef = LensRef

-- | A trivial 'LensRef' that focuses on the whole structure
wholeLensRef :: r a -> LensRef r a
wholeLensRef whole = LensRef whole id

-- | /Zooms/ into a smaller part of the structure
zoomLensRef :: LensRef r a -> Lens' a b -> LensRef r b
zoomLensRef (LensRef whole part) sub = LensRef whole (part . sub)

-- | Read the reference through the lens.
readLensRef :: (Functor m, ReadWriteRef r m) => LensRef r a -> m a
readLensRef (LensRef whole part) = fmap (view part) (readRef whole)

-- | Write the reference through the lens.
-- The 'ModifyRef' constraint is necessary to ensure we read and write
-- the same structure!
writeLensRef :: ModifyRef r m => LensRef r a -> a -> m ()
writeLensRef (LensRef whole part) = modifyRef whole . set part

-- | Modify the reference through the lens.
modifyLensRef :: ModifyRef r m => LensRef r a -> (a -> a) -> m ()
modifyLensRef (LensRef whole part) = modifyRef whole . over part

overWith :: Lens' z a -> (a -> (a, b)) -> z -> (z, b)
overWith l f z =
  let a = view l z
      (a', b) = f a
      z' = set l a' z
  in (z', b)

-- | Atomically modify the reference through the lens.
atomicModifyLensRef :: AtomicRef r m => LensRef r a -> (a -> (a, b)) -> m b
atomicModifyLensRef (LensRef whole part) = atomicModifyRef whole . overWith part

instance (Functor m, ModifyRef r m) => ReadWriteRef (LensRef r) m where
  readRef = readLensRef
  writeRef = writeLensRef

instance (Functor m, ModifyRef r m) => ModifyRef (LensRef r) m where
  modifyRef = modifyLensRef

instance (Functor m, AtomicRef r m) => AtomicRef (LensRef r) m where
  atomicModifyRef = atomicModifyLensRef