packages feed

optics-extra-0.4: src/Optics/Zoom.hs

{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
module Optics.Zoom
  (
    -- * Zoom
    Zoom(..)
    -- * Magnify
  , Magnify(..)
  , MagnifyMany(..)
  ) where

import Control.Monad.Reader (ReaderT (..), MonadReader)
import Control.Monad.State (MonadState (..))
import Control.Monad.Trans.Error (Error, ErrorT (..))
import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
import Control.Monad.Trans.Identity (IdentityT (..))
import Control.Monad.Trans.List (ListT (..))
import Control.Monad.Trans.Maybe (MaybeT (..))
import qualified Control.Monad.Trans.RWS.Lazy as L
import qualified Control.Monad.Trans.RWS.Strict as S
import qualified Control.Monad.Trans.State.Lazy as L
import qualified Control.Monad.Trans.State.Strict as S
import qualified Control.Monad.Trans.Writer.Lazy as L
import qualified Control.Monad.Trans.Writer.Strict as S

import Optics.Core
import Optics.Internal.Utils
import Optics.Extra.Internal.Zoom

-- $setup
-- >>> import Data.Monoid
-- >>> import Control.Monad.Reader (runReader, ask)
-- >>> import Optics.State
-- >>> import Optics.State.Operators
-- >>> import Optics.View

-- Chosen so that they have lower fixity than ('%=').
infixr 2 `zoom`, `zoomMaybe`, `zoomMany`
infixr 2 `magnify`, `magnifyMaybe`, `magnifyMany`

------------------------------------------------------------------------------
-- Zoom
------------------------------------------------------------------------------

-- | This class allows us to 'zoom' in, changing the 'State' supplied by many
-- different monad transformers, potentially quite deep in a monad transformer
-- stack.
--
-- Its functions can be used to run a monadic action in a larger 'State' than it
-- was defined in, using a 'Lens'', an 'AffineTraversal'' or a 'Traversal''.
--
-- This is commonly used to lift actions in a simpler 'State' 'Monad' into a
-- 'State' 'Monad' with a larger 'State' type.
--
-- When used with a 'Traversal'' over multiple values, the actions for each
-- target are executed sequentially and the results are aggregated.
--
-- This can be used to edit pretty much any 'Monad' transformer stack with a
-- 'State' in it!
--
-- >>> flip L.evalState ('a','b') $ zoom _1 $ use equality
-- 'a'
--
-- >>> flip S.execState ('a','b') $ zoom _1 $ equality .= 'c'
-- ('c','b')
--
-- >>> flip L.execState [(1,2),(3,4)] $ zoomMany traversed $ _2 %= (*10)
-- [(1,20),(3,40)]
--
-- >>> flip S.runState [('a',"b"),('c',"d")] $ zoomMany traversed $ _2 <%= (\x -> x <> x)
-- ("bbdd",[('a',"bb"),('c',"dd")])
--
-- >>> flip S.evalState ("a","b") $ zoomMany each (use equality)
-- "ab"
--
class
  (MonadState s m, MonadState t n
  ) => Zoom m n s t | m -> s, n -> t, m t -> n, n s -> m where
  zoom
    :: Is k A_Lens
    => Optic' k is t s
    -> m c
    -> n c

  zoomMaybe
    :: Is k An_AffineTraversal
    => Optic' k is t s
    -> m c
    -> n (Maybe c)

  zoomMany
    :: (Is k A_Traversal, Monoid c)
    => Optic' k is t s
    -> m c
    -> n c

instance Monad m => Zoom (S.StateT s m) (S.StateT t m) s t where
  zoom      o = \(S.StateT m) -> S.StateT $ stateZoom      o m
  zoomMaybe o = \(S.StateT m) -> S.StateT $ stateZoomMaybe o m
  zoomMany  o = \(S.StateT m) -> S.StateT $ stateZoomMany  o m
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance Monad m => Zoom (L.StateT s m) (L.StateT t m) s t where
  zoom      o = \(L.StateT m) -> L.StateT $ stateZoom      o m
  zoomMaybe o = \(L.StateT m) -> L.StateT $ stateZoomMaybe o m
  zoomMany  o = \(L.StateT m) -> L.StateT $ stateZoomMany  o m
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance Zoom m n s t => Zoom (ReaderT e m) (ReaderT e n) s t where
  zoom      o = \(ReaderT m) -> ReaderT (zoom      o . m)
  zoomMaybe o = \(ReaderT m) -> ReaderT (zoomMaybe o . m)
  zoomMany  o = \(ReaderT m) -> ReaderT (zoomMany  o . m)
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance Zoom m n s t => Zoom (IdentityT m) (IdentityT n) s t where
  zoom      o = \(IdentityT m) -> IdentityT (zoom      o m)
  zoomMaybe o = \(IdentityT m) -> IdentityT (zoomMaybe o m)
  zoomMany  o = \(IdentityT m) -> IdentityT (zoomMany  o m)
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance (Monoid w, Monad m) => Zoom (S.RWST r w s m) (S.RWST r w t m) s t where
  zoom      o = \(S.RWST m) -> S.RWST $ rwsZoom      o m
  zoomMaybe o = \(S.RWST m) -> S.RWST $ rwsZoomMaybe o m
  zoomMany  o = \(S.RWST m) -> S.RWST $ rwsZoomMany  o m
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance (Monoid w, Monad m) => Zoom (L.RWST r w s m) (L.RWST r w t m) s t where
  zoom      o = \(L.RWST m) -> L.RWST $ rwsZoom      o m
  zoomMaybe o = \(L.RWST m) -> L.RWST $ rwsZoomMaybe o m
  zoomMany  o = \(L.RWST m) -> L.RWST $ rwsZoomMany  o m
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance (Monoid w, Zoom m n s t) => Zoom (S.WriterT w m) (S.WriterT w n) s t where
  zoom      o = S.WriterT #.                 zoom      o .# S.runWriterT
  zoomMaybe o = S.WriterT #. fmap shuffleW . zoomMaybe o .# S.runWriterT
  zoomMany  o = S.WriterT #.                 zoomMany  o .# S.runWriterT
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance (Monoid w, Zoom m n s t) => Zoom (L.WriterT w m) (L.WriterT w n) s t where
  zoom      o = L.WriterT #.                 zoom      o .# L.runWriterT
  zoomMaybe o = L.WriterT #. fmap shuffleW . zoomMaybe o .# L.runWriterT
  zoomMany  o = L.WriterT #.                 zoomMany  o .# L.runWriterT
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance Zoom m n s t => Zoom (ListT m) (ListT n) s t where
  zoom      o = ListT #.                  zoom      o .# runListT
  zoomMaybe o = ListT #. fmap sequenceA . zoomMaybe o .# runListT
  zoomMany  o = ListT #.                  zoomMany  o .# runListT
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance Zoom m n s t => Zoom (MaybeT m) (MaybeT n) s t where
  zoom o =
    MaybeT #. zoom o .# runMaybeT
  zoomMaybe o =
    MaybeT #. fmap (getMay . shuffleMay) . zoomMaybe o . fmap May .# runMaybeT
  zoomMany o =
    MaybeT #. fmap getMay . zoomMany o . fmap May .# runMaybeT
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance (Error e, Zoom m n s t) => Zoom (ErrorT e m) (ErrorT e n) s t where
  zoom o =
    ErrorT #. zoom o .# runErrorT
  zoomMaybe o =
    ErrorT #. fmap (getErr . shuffleErr) . zoomMaybe o . fmap Err .# runErrorT
  zoomMany o =
    ErrorT #. fmap getErr . zoomMany o . fmap Err .# runErrorT
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

instance Zoom m n s t => Zoom (ExceptT e m) (ExceptT e n) s t where
  zoom o =
    ExceptT #. zoom o .# runExceptT
  zoomMaybe o =
    ExceptT #. fmap (getErr . shuffleErr) . zoomMaybe o . fmap Err .# runExceptT
  zoomMany o =
    ExceptT #. fmap getErr . zoomMany o . fmap Err .# runExceptT
  {-# INLINE zoom #-}
  {-# INLINE zoomMaybe #-}
  {-# INLINE zoomMany #-}

------------------------------------------------------------------------------
-- Magnify
------------------------------------------------------------------------------

-- | This class allows us to 'magnify' part of the environment, changing the
-- environment supplied by many different 'Monad' transformers. Unlike 'zoom'
-- this can change the environment of a deeply nested 'Monad' transformer.
--
-- Its functions can be used to run a monadic action in a larger environment
-- than it was defined in, using a 'Getter' or an 'AffineFold'.
--
-- They act like 'Control.Monad.Reader.Class.local', but can in many cases
-- change the type of the environment as well.
--
-- They're commonly used to lift actions in a simpler 'Reader' 'Monad' into a
-- 'Monad' with a larger environment type.
--
-- They can be used to edit pretty much any 'Monad' transformer stack with an
-- environment in it:
--
-- >>> (1,2) & magnify _2 (+1)
-- 3
--
-- >>> flip runReader (1,2) $ magnify _1 ask
-- 1
--
-- >>> flip runReader (1,2,[10..20]) $ magnifyMaybe (_3 % _tail) ask
-- Just [11,12,13,14,15,16,17,18,19,20]
--
class
  (MonadReader b m, MonadReader a n
  ) => Magnify m n b a | m -> b, n -> a, m a -> n, n b -> m where
  magnify
    :: Is k A_Getter
    => Optic' k is a b
    -> m c
    -> n c

  magnifyMaybe
    :: Is k An_AffineFold
    => Optic' k is a b
    -> m c
    -> n (Maybe c)

-- | Extends 'Magnify' with an ability to magnify using a 'Fold' over multiple
-- targets so that actions for each one are executed sequentially and the
-- results are aggregated.
--
-- There is however no sensible instance of 'MagnifyMany' for 'StateT'.
class
  (MonadReader b m, MonadReader a n, Magnify m n b a
  ) => MagnifyMany m n b a | m -> b, n -> a, m a -> n, n b -> m where
  magnifyMany
    :: (Is k A_Fold, Monoid c)
    => Optic' k is a b
    -> m c
    -> n c

-- | @
-- 'magnify'      = 'views'
-- 'magnifyMaybe' = 'previews'
-- @
instance Magnify ((->) b) ((->) a) b a where
  magnify      = views
  magnifyMaybe = previews
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

-- | @
-- 'magnifyMany' = 'foldMapOf'
-- @
instance MagnifyMany ((->) b) ((->) a) b a where
  magnifyMany = foldMapOf
  {-# INLINE magnifyMany #-}

instance Monad m => Magnify (ReaderT b m) (ReaderT a m) b a where
  magnify o = \(ReaderT m) ->
    ReaderT $ \r -> getEffect (views o (Effect #. m) r)
  magnifyMaybe o = \(ReaderT m) ->
    ReaderT $ \r -> traverse getEffect (previews o (Effect #. m) r)
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance Monad m => MagnifyMany (ReaderT b m) (ReaderT a m) b a where
  magnifyMany o = \(ReaderT m) ->
    ReaderT $ \r -> getEffect (foldMapOf o (Effect #. m) r)
  {-# INLINE magnifyMany #-}

instance (Monad m, Monoid w) => Magnify (S.RWST b w s m) (S.RWST a w s m) b a where
  magnify      o = \(S.RWST m) -> S.RWST $ rwsMagnify      o m
  magnifyMaybe o = \(S.RWST m) -> S.RWST $ rwsMagnifyMaybe o m
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance
  (Monad m, Monoid w
  ) => MagnifyMany (S.RWST b w s m) (S.RWST a w s m) b a where
  magnifyMany o = \(S.RWST m) -> S.RWST $ rwsMagnifyMany  o m
  {-# INLINE magnifyMany #-}

instance (Monad m, Monoid w) => Magnify (L.RWST b w s m) (L.RWST a w s m) b a where
  magnify      o = \(L.RWST m) -> L.RWST $ rwsMagnify      o m
  magnifyMaybe o = \(L.RWST m) -> L.RWST $ rwsMagnifyMaybe o m
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance
  (Monad m, Monoid w
  ) => MagnifyMany (L.RWST b w s m) (L.RWST a w s m) b a where
  magnifyMany o = \(L.RWST m) -> L.RWST $ rwsMagnifyMany  o m
  {-# INLINE magnifyMany #-}

instance Magnify m n b a => Magnify (IdentityT m) (IdentityT n) b a where
  magnify      o = \(IdentityT m) -> IdentityT (magnify      o m)
  magnifyMaybe o = \(IdentityT m) -> IdentityT (magnifyMaybe o m)
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance MagnifyMany m n b a => MagnifyMany (IdentityT m) (IdentityT n) b a where
  magnifyMany o = \(IdentityT m) -> IdentityT (magnifyMany  o m)
  {-# INLINE magnifyMany #-}

instance Magnify m n b a => Magnify (S.StateT s m) (S.StateT s n) b a where
  magnify      o = \(S.StateT m) -> S.StateT $ magnify o . m
  magnifyMaybe o = \(S.StateT m) -> S.StateT $ \s ->
    fmap (shuffleS s) $ magnifyMaybe o (m s)
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

-- No instance for MagnifyMany (S.StateT s m) (S.StateT s n) b a

instance Magnify m n b a => Magnify (L.StateT s m) (L.StateT s n) b a where
  magnify      o = \(L.StateT m) -> L.StateT $ magnify o . m
  magnifyMaybe o = \(L.StateT m) -> L.StateT $ \s ->
    fmap (shuffleS s) $ magnifyMaybe o (m s)
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

-- No instance for MagnifyMany (L.StateT s m) (L.StateT s n) b a

instance
  (Monoid w, Magnify m n b a
  ) => Magnify (S.WriterT w m) (S.WriterT w n) b a where
  magnify      o = S.WriterT #.                 magnify      o .# S.runWriterT
  magnifyMaybe o = S.WriterT #. fmap shuffleW . magnifyMaybe o .# S.runWriterT
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance
  (Monoid w, MagnifyMany m n b a
  ) => MagnifyMany (S.WriterT w m) (S.WriterT w n) b a where
  magnifyMany o = S.WriterT #. magnifyMany o .# S.runWriterT
  {-# INLINE magnifyMany #-}

instance
  (Monoid w, Magnify m n b a
  ) => Magnify (L.WriterT w m) (L.WriterT w n) b a where
  magnify      o = L.WriterT #.                 magnify      o .# L.runWriterT
  magnifyMaybe o = L.WriterT #. fmap shuffleW . magnifyMaybe o .# L.runWriterT
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance
  (Monoid w, MagnifyMany m n b a
  ) => MagnifyMany (L.WriterT w m) (L.WriterT w n) b a where
  magnifyMany o = L.WriterT #. magnifyMany o .# L.runWriterT
  {-# INLINE magnifyMany #-}

instance Magnify m n b a => Magnify (ListT m) (ListT n) b a where
  magnify      o = ListT #.                  magnify      o .# runListT
  magnifyMaybe o = ListT #. fmap sequenceA . magnifyMaybe o .# runListT
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance MagnifyMany m n b a => MagnifyMany (ListT m) (ListT n) b a where
  magnifyMany o = ListT #. magnifyMany o .# runListT
  {-# INLINE magnifyMany #-}

instance Magnify m n b a => Magnify (MaybeT m) (MaybeT n) b a where
  magnify o = MaybeT #. magnify o .# runMaybeT
  magnifyMaybe o =
    MaybeT #. fmap (getMay . shuffleMay) . magnifyMaybe o . fmap May .# runMaybeT
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance MagnifyMany m n b a => MagnifyMany (MaybeT m) (MaybeT n) b a where
  magnifyMany o = MaybeT #. fmap getMay . magnifyMany o . fmap May .# runMaybeT
  {-# INLINE magnifyMany #-}

instance (Error e, Magnify m n b a) => Magnify (ErrorT e m) (ErrorT e n) b a where
  magnify o = ErrorT #. magnify o .# runErrorT
  magnifyMaybe o =
    ErrorT #. fmap (getErr . shuffleErr) . magnifyMaybe o . fmap Err .# runErrorT
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance
  (Error e, MagnifyMany m n b a
  ) => MagnifyMany (ErrorT e m) (ErrorT e n) b a where
  magnifyMany o = ErrorT #. fmap getErr . magnifyMany o . fmap Err .# runErrorT
  {-# INLINE magnifyMany #-}

instance Magnify m n b a => Magnify (ExceptT e m) (ExceptT e n) b a where
  magnify o = ExceptT #. magnify o .# runExceptT
  magnifyMaybe o =
    ExceptT #. fmap (getErr . shuffleErr) . magnifyMaybe o . fmap Err .# runExceptT
  {-# INLINE magnify #-}
  {-# INLINE magnifyMaybe #-}

instance MagnifyMany m n b a => MagnifyMany (ExceptT e m) (ExceptT e n) b a where
  magnifyMany o = ExceptT #. fmap getErr . magnifyMany o . fmap Err .# runExceptT
  {-# INLINE magnifyMany #-}