endo-0.3.0.0: src/Data/Monoid/Endo/Apply.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-} -- GeneralizedNewtypeDeriving failed.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TypeFamilies #-}
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
{-# LANGUAGE DeriveDataTypeable #-}
#endif
#if MIN_VERSION_transformers(0,5,0) || MIN_VERSION_base(4,9,0)
#define HAVE_FUNCTOR_CLASSES
#endif
-- |
-- Module: $HEADER$
-- Description: ApplyEndo provides easier endomorphism evaluation in cases
-- when there is an "obvious" default value.
-- Copyright: (c) 2015-2016, Peter Trško
-- License: BSD3
--
-- Maintainer: peter.trsko@gmail.com
-- Stability: experimental
-- Portability: CPP, DeriveFoldable, DeriveFunctor, DeriveDataTypeable,
-- DeriveGeneric, DeriveTraversable, FlexibleInstances,
-- NoImplicitPrelude, TypeFamilies
--
-- 'ApplyEndo' provides easier endomorphism evaluation in cases when there is an
-- \"obvious\" default value.
module Data.Monoid.Endo.Apply
(
-- * ApplyEndo
ApplyEndo(..)
, apply
, applyF
-- ** ApplyEndo Mempty
, Mempty
, applyMempty
, applyMempty_
, joinApplyMempty
-- ** ApplyEndo Def
--
-- $applyEndoDef
, Def
, applyDef
, applyDef_
, joinApplyDef
-- ** ApplyEndo Reader
, Reader
, applyReader
, applyReaderWith
, joinApplyReader
-- ** ApplyEndo Modify
, Modify
, applyModify
, joinApplyModify
-- ** ApplyEndo Modify'
, Modify'
, applyModify'
, joinApplyModify'
)
where
import Prelude (seq)
import Control.Applicative (Applicative(pure))
import Control.Monad
( Monad((>>=))
#ifdef HAVE_APPLICATIVE_MONAD
, void
#else
, liftM
#endif
)
import Data.Foldable (Foldable)
import Data.Function
( (.)
, ($)
#ifndef HAVE_APPLICATIVE_MONAD
, const
#endif
)
import Data.Functor (Functor, (<$>))
#ifdef HAVE_FUNCTOR_CLASSES
import Data.Functor.Classes
( Eq1
, Ord1
, Read1(liftReadsPrec)
, Show1(liftShowsPrec)
, readsData
, readsUnaryWith
, showsUnaryWith
)
#endif
import Data.Functor.Identity (Identity(runIdentity))
import Data.Monoid (Endo(Endo, appEndo), Monoid(mempty))
import Data.Traversable (Traversable)
import GHC.Generics (Generic, Generic1)
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
import Data.Data (Data, Typeable)
#endif
import Control.Monad.Reader.Class (MonadReader)
import qualified Control.Monad.Reader.Class as MonadReader (asks)
import Control.Monad.State.Class (MonadState)
import qualified Control.Monad.State.Class as MonadState (state)
import Data.Default.Class (Default(def))
import Data.Monoid.Endo.FromEndo (FromEndo(..))
-- | There are cases when it is \"obvious\" what is the default value, which
-- should be modified by the endomorphism. This type is a result of such
-- endomorphism application and it uses phantom type @t@ as distinguishing
-- property, which decides what is the correct \"default value\".
newtype ApplyEndo t f a = ApplyEndo {applyEndo :: f a}
deriving
( Applicative
, Foldable
, Functor
, Generic
, Generic1
, Monad
, Traversable
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
, Data
, Typeable
#endif
#ifdef HAVE_FUNCTOR_CLASSES
, Eq1
, Ord1
#endif
)
#ifdef HAVE_FUNCTOR_CLASSES
instance Read1 f => Read1 (ApplyEndo t f) where
liftReadsPrec rp rl =
readsData $ readsUnaryWith (liftReadsPrec rp rl) "ApplyEndo" ApplyEndo
instance Show1 f => Show1 (ApplyEndo t f) where
liftShowsPrec sp sl d (ApplyEndo x) =
showsUnaryWith (liftShowsPrec sp sl) "ApplyEndo" d x
#endif
-- HAVE_FUNCTOR_CLASSES
-- | Apply endomorphism using provided \"default\" value.
apply :: Applicative f => a -> Endo a -> ApplyEndo t f a
apply defaultValue (Endo f) = ApplyEndo . pure $ f defaultValue
{-# INLINE apply #-}
-- | Similar as 'apply', but expects 'Endo' to be wrapped by a 'Functor'.
applyF :: Functor f => a -> f (Endo a) -> ApplyEndo t f a
applyF defaultValue endo = ApplyEndo $ (`appEndo` defaultValue) <$> endo
-- {{{ ApplyEndo Mempty -------------------------------------------------------
-- | Type tag identifying usage of 'mempty' from 'Monoid'.
data Mempty
deriving
( Generic
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
, Typeable
#endif
)
instance (Applicative f, Monoid a) => FromEndo (ApplyEndo Mempty f a) where
type EndoOperatedOn (ApplyEndo Mempty f a) = a
fromEndo = apply mempty
-- | Constrained version of 'applyEndo'. Usage example:
--
-- @
-- applyMempty . fromEndo :: ('Applicative' f, 'Monoid' a) => 'Endo' a -> f a
-- @
applyMempty :: Monoid a => ApplyEndo Mempty f a -> f a
applyMempty = applyEndo
{-# INLINE applyMempty #-}
-- | Same as 'applyMempty', but 'Applicative' functor is specialized to
-- 'Identity' functor and evaluated.
--
-- Examples:
--
-- >>> fromEndoWith applyMempty_ $ foldEndo (+1) [(*10), (+42)] :: Int
-- 421
-- >>> fromEndoWith applyMempty_ $ dualFoldEndo (+1) [(*10), (+42)] :: Int
-- 52
applyMempty_ :: Monoid a => ApplyEndo Mempty Identity a -> a
applyMempty_ = runIdentity . applyMempty
{-# INLINE applyMempty_ #-}
-- | Evaluates 'ApplyEndo' in a 'Monad' by joining it with the monad it
-- contains. It can be also viewed as a variant of 'applyMempty' defined as:
--
-- @
-- 'joinApplyMempty' = ('>>=' 'applyMempty')
-- @
joinApplyMempty
:: ( Monad m
, Monoid a
#ifndef HAVE_APPLICATIVE_MONAD
, Applicative m
#endif
)
=> m (ApplyEndo Mempty m a) -> m a
joinApplyMempty = (>>= applyMempty)
{-# INLINE joinApplyMempty #-}
-- }}} ApplyEndo Mempty -------------------------------------------------------
-- {{{ ApplyEndo Def ----------------------------------------------------------
-- $applyEndoDef
--
-- Apply endomorphism to a default value 'def' from 'Default'. See also
-- following packages:
--
-- * <https://hackage.haskell.org/package/data-default-extra data-default-extra>
--
-- * <https://hackage.haskell.org/package/data-default data-default>
--
-- Both of those packages provide additional instances to 'Default' type
-- class.
-- | Type tag identifying usage of 'def' from 'Default'.
data Def
deriving
( Generic
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
, Typeable
#endif
)
instance (Applicative f, Default a) => FromEndo (ApplyEndo Def f a) where
type EndoOperatedOn (ApplyEndo Def f a) = a
fromEndo = apply def
-- | Constrained version of 'applyEndo'. Usage example:
--
-- @
-- applyDef . fromEndo :: ('Applicative' f, 'Default' a) => 'Endo' a -> f a
-- @
applyDef :: (Applicative f, Default a) => ApplyEndo Def f a -> f a
applyDef = applyEndo
{-# INLINE applyDef #-}
-- | Same as 'applyDef', but 'Applicative' functor is specialized to 'Identity'
-- functor and evaluated.
--
-- Examples:
--
-- >>> fromEndoWith applyDef_ $ foldEndo (+1) [(*10), (+42)] :: Int
-- 421
-- >>> fromEndoWith applyDef_ $ dualFoldEndo (+1) [(*10), (+42)] :: Int
-- 52
applyDef_ :: Default a => ApplyEndo Def Identity a -> a
applyDef_ = runIdentity . applyDef
{-# INLINE applyDef_ #-}
-- | Evaluates 'ApplyEndo' in a 'Monad' by joining it with the monad it
-- contains. It can be also viewed as a variant of 'applyDef' defined as:
--
-- @
-- 'joinApplyDef' = ('>>=' 'applyDef')
-- @
joinApplyDef
:: ( Monad m
, Default a
#ifndef HAVE_APPLICATIVE_MONAD
, Applicative m
#endif
)
=> m (ApplyEndo Def m a) -> m a
joinApplyDef = (>>= applyDef)
{-# INLINE joinApplyDef #-}
-- }}} ApplyEndo Def ----------------------------------------------------------
-- {{{ ApplyEndo Reader -------------------------------------------------------
-- | Type tag identifying usage of 'MonadReader.asks' operation in 'FromEndo'
-- instance of 'ApplyEndo'.
data Reader
deriving
( Generic
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
, Typeable
#endif
)
-- | Evaluates 'ApplyEndo' in terms of 'MonadReader.asks' operation:
--
-- @
-- 'fromEndo' = 'ApplyEndo' . 'MonadReader.asks' . 'appEndo'
-- @
instance MonadReader r m => FromEndo (ApplyEndo Reader m r) where
type EndoOperatedOn (ApplyEndo Reader m r) = r
fromEndo = ApplyEndo . MonadReader.asks . appEndo
-- | Evaluates 'ApplyEndo' in terms of 'MonadReader.asks' operation.
--
-- This @(->) r@ is a valid 'MonadReader' instance, therefore, this is a valid
-- use case:
--
-- >>> (applyReader . fromEndo $ foldEndo (*10) (+1)) 0 :: Int
-- 10
applyReader :: MonadReader r m => ApplyEndo Reader m r -> m r
applyReader = applyEndo
-- | Evaluates 'ApplyEndo' in terms of 'MonadReader.asks' operation and then
-- evaluates the resalt using provided function.
--
-- This @(->) r@ is a valid 'MonadReader' instance, therefore, this is a valid
-- use case:
--
-- >>> applyReaderWith ($ 0) . fromEndo $ foldEndo (*10) (+1) :: Int
-- 10
applyReaderWith :: MonadReader r m => (m r -> a) -> ApplyEndo Reader m r -> a
applyReaderWith = (. applyEndo)
-- | Evaluates 'ApplyEndo' in a 'Monad' by joining it with the monad it
-- contains. It can be also viewed as a variant of 'applyReader' defined as:
--
-- @
-- 'joinApplyReader' = ('>>=' 'applyReader')
-- @
joinApplyReader :: MonadReader r m => m (ApplyEndo Reader m r) -> m r
joinApplyReader = (>>= applyEndo)
-- }}} ApplyEndo Reader -------------------------------------------------------
-- {{{ ApplyEndo Modify -------------------------------------------------------
-- | Type tag identifying usage of 'MonadState.state' operation in 'FromEndo'
-- instance of 'ApplyEndo'.
data Modify
deriving
( Generic
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
, Typeable
#endif
)
-- | Evaluates 'ApplyEndo' in terms of 'MonadState.state' operation:
--
-- @
-- 'fromEndo' e = 'ApplyEndo' . 'MonadState.state' '$' \\s ->
-- let s' = 'appEndo' e s in (s', s')
-- @
instance MonadState s m => FromEndo (ApplyEndo Modify m s) where
type EndoOperatedOn (ApplyEndo Modify m s) = s
fromEndo e =
ApplyEndo . MonadState.state $ \s -> let s' = appEndo e s in (s', s')
-- | Evaluates 'ApplyEndo' in terms of 'MonadState.state' operation.
applyModify :: MonadState s m => ApplyEndo Modify m s -> m s
applyModify = applyEndo
-- | Evaluates 'ApplyEndo' in a 'Monad' by joining it with the monad it
-- contains. It can be also viewed as a variant of 'applyModify' defined as:
--
-- @
-- 'joinApplyModify' = ('>>=' 'applyModify')
-- @
joinApplyModify :: MonadState s m => m (ApplyEndo Modify m s) -> m s
joinApplyModify = (>>= applyEndo)
-- | Same as 'Modify', but strictness is implied.
data Modify'
deriving
( Generic
#ifdef HAVE_KIND_POLYMORPHIC_TYPEABLE
, Typeable
#endif
)
-- | Evaluates 'ApplyEndo' in terms of 'MonadState.state' operation:
--
-- @
-- 'fromEndo' ('Endo' f) = 'ApplyEndo' . 'MonadState.state' $ \\s ->
-- let s' = f s in s' \`seq\` (s', s')
-- @
instance MonadState s m => FromEndo (ApplyEndo Modify' m s) where
type EndoOperatedOn (ApplyEndo Modify' m s) = s
fromEndo (Endo f) =
ApplyEndo . MonadState.state $ \s -> let s' = f s in s' `seq` (s', s')
-- | Evaluates 'ApplyEndo' in terms of 'MonadState.state' operation.
applyModify' :: MonadState r m => ApplyEndo Modify' m () -> m ()
applyModify' = void . applyEndo
-- | Evaluates 'ApplyEndo' in a 'Monad' by joining it with the monad it
-- contains. It can be also viewed as a variant of 'applyModify'' defined as:
--
-- @
-- 'joinApplyModify'' = ('>>=' 'applyModify'')
-- @
joinApplyModify' :: MonadState r m => m (ApplyEndo Modify' m r) -> m r
joinApplyModify' = (>>= applyEndo)
-- }}} ApplyEndo Modify -------------------------------------------------------
-- {{{ Helper functions (not exported) ----------------------------------------
#ifndef HAVE_APPLICATIVE_MONAD
void :: Monad m => m a -> m ()
void = liftM $ const ()
#endif
-- {{{ Helper functions (not exported) ----------------------------------------