changeset-lens-0.1.0.0: src/Control/Monad/Changeset/Lens/Ixed.hs
{-# LANGUAGE UndecidableInstances #-}
module Control.Monad.Changeset.Lens.Ixed where
-- base
import Data.Foldable (Foldable)
import Data.Function ((&))
import Data.Monoid (First (..))
import Prelude hiding (Foldable (..))
-- lens
import Control.Lens (Index, IxValue, Ixed (..), (%~))
-- containers
import Data.Map.Strict (Map)
-- monoidal-containers
import Data.Map.Monoidal (MonoidalMap, foldlWithKey', singleton)
-- monoid-extras
import Data.Monoid.RightAction
-- indexed-traversal
import Data.Foldable.WithIndex (FoldableWithIndex)
import Data.Functor.WithIndex (FunctorWithIndex)
import Data.Traversable.WithIndex (TraversableWithIndex (..))
-- witherable
import Witherable (Filterable (..), FilterableWithIndex (..), Witherable (..), WitherableWithIndex)
-- changeset
import Control.Monad.Changeset.Class (MonadChangeset (..))
{- | Edit parts of an indexed datastructure.
The state datastructure is usually a container, such as a 'Data.Map.Map'.
Changes to an element of that structure are promoted to a change of the whole structure
by pairing them with an 'Index' that points to a specific position in the structure.
Note: Internally, an 'IxedChangeset' is represented as a map,
and the monoid structure is pointwise.
This is because we assume that all different indices refer to different positions,
so changes on different indices commute.
-}
newtype IxedChangeset s w = IxedChangeset
{getIxedChangeset :: MonoidalMap (Index s) w}
deriving instance (Ord (Index s), Semigroup w) => Semigroup (IxedChangeset s w)
deriving instance (Ord (Index s), Monoid w) => Monoid (IxedChangeset s w)
deriving instance (Eq (Index s), Eq w) => Eq (IxedChangeset s w)
deriving instance (Ord (Index s), Ord w) => Ord (IxedChangeset s w)
deriving instance (Show (Index s), Show w) => Show (IxedChangeset s w)
deriving instance (Ord (Index s), Read (Index s), Read w) => Read (IxedChangeset s w)
deriving instance Functor (IxedChangeset s)
deriving instance Foldable (IxedChangeset s)
deriving instance Traversable (IxedChangeset s)
deriving instance (Index s ~ i) => FunctorWithIndex i (IxedChangeset s)
deriving instance (Index s ~ i) => FoldableWithIndex i (IxedChangeset s)
instance (Index s ~ i) => TraversableWithIndex i (IxedChangeset s) where
itraverse f = fmap IxedChangeset . itraverse f . getIxedChangeset
instance Filterable (IxedChangeset s) where
mapMaybe f = IxedChangeset . mapMaybe f . getIxedChangeset
instance (Index s ~ i) => FilterableWithIndex i (IxedChangeset s)
instance (Ord (Index s)) => Witherable (IxedChangeset s) where
wither f = fmap IxedChangeset . wither f . getIxedChangeset
instance (Index s ~ i, Ord i) => WitherableWithIndex i (IxedChangeset s)
instance (RightAction w (IxValue s), Ixed s) => RightAction (IxedChangeset s w) s where
actRight s IxedChangeset {getIxedChangeset} = foldlWithKey' (\s' i w -> s' & ix i %~ flip actRight w) s getIxedChangeset
-- | Create an 'IxedChangeset' by pointing to a position in @s@, and specifying a change @w@ to the value at that position.
ixedChangeset ::
-- | A position in @s@. For example, @s@ might be a map, and @'Index' s@ a key.
Index s ->
-- | A change to the element in @s@ at the given position. Typically, we expect @'RightAction' w ('IxValue' s)@ to hold.
w ->
IxedChangeset s w
ixedChangeset i = IxedChangeset . singleton i
-- | Changes to the elements of a 'Map'.
type MapIxedChangeset k a = IxedChangeset (Map k a) a
{- | Change a value at a given index.
Essentially, this applies 'ixedChangeset'.
Example:
@
-- Increments the value at key i
i |>! Increment
@
-}
(|>!) :: (MonadChangeset s (IxedChangeset s w) m) => Index s -> w -> m ()
index |>! w = change $ ixedChangeset index w
{- | Set a value at a given index.
A shorthand for '|>!' in combination with 'First'.
Example:
@
-- Sets the value at key i to a
i .! a
@
-}
(.!) :: (MonadChangeset s (IxedChangeset s (First (IxValue s))) m) => Index s -> IxValue s -> m ()
index .! w = index |>! First (Just w)