{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -- | 'Patch'es on 'Map' that can insert, delete, and move values from one key to -- another module Data.Patch.MapWithMove ( PatchMapWithMove ( PatchMapWithMove , unPatchMapWithMove , .. ) , patchMapWithMove , patchMapWithMoveInsertAll , insertMapKey , moveMapKey , swapMapKey , deleteMapKey , unsafePatchMapWithMove , patchMapWithMoveNewElements , patchMapWithMoveNewElementsMap , patchThatSortsMapWith , patchThatChangesAndSortsMapWith , patchThatChangesMap -- * Node Info , NodeInfo ( NodeInfo , _nodeInfo_to , _nodeInfo_from , .. ) , bitraverseNodeInfo , nodeInfoMapFrom , nodeInfoMapMFrom , nodeInfoSetTo -- * From , From ( From_Insert , From_Delete , From_Move , .. ) , bitraverseFrom -- * To , To ) where import Data.Coerce import Data.Kind (Type) import Data.Patch.Class import Data.Patch.MapWithPatchingMove (PatchMapWithPatchingMove(..), To) import qualified Data.Patch.MapWithPatchingMove as PM -- already a transparent synonym import Control.Lens import Data.List import Data.Map (Map) import qualified Data.Map as Map import Data.Proxy #if !MIN_VERSION_base(4,11,0) import Data.Semigroup (Semigroup (..)) #endif import Data.Traversable (foldMapDefault) -- | Patch a Map with additions, deletions, and moves. Invariant: If key @k1@ -- is coming from @From_Move k2@, then key @k2@ should be going to @Just k1@, -- and vice versa. There should never be any unpaired From/To keys. newtype PatchMapWithMove k (v :: Type) = PatchMapWithMove' { -- | Extract the underlying 'PatchMapWithPatchingMove k (Proxy v)' unPatchMapWithMove' :: PatchMapWithPatchingMove k (Proxy v) } deriving ( Show, Read, Eq, Ord -- Haddock cannot handle documentation here before GHC 8.6 , #if __GLASGOW_HASKELL__ >= 806 -- | Compose patches having the same effect as applying the -- patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . -- 'applyAlways' q@ #endif Semigroup , Monoid ) pattern Coerce :: Coercible a b => a -> b pattern Coerce x <- (coerce -> x) where Coerce x = coerce x {-# COMPLETE PatchMapWithMove #-} pattern PatchMapWithMove :: Map k (NodeInfo k v) -> PatchMapWithMove k v -- | Extract the representation of the 'PatchMapWithMove' as a map of -- 'NodeInfo'. unPatchMapWithMove :: PatchMapWithMove k v -> Map k (NodeInfo k v) pattern PatchMapWithMove { unPatchMapWithMove } = PatchMapWithMove' (PatchMapWithPatchingMove (Coerce unPatchMapWithMove)) _PatchMapWithMove :: Iso (PatchMapWithMove k0 v0) (PatchMapWithMove k1 v1) (Map k0 (NodeInfo k0 v0)) (Map k1 (NodeInfo k1 v1)) _PatchMapWithMove = iso unPatchMapWithMove PatchMapWithMove instance Functor (PatchMapWithMove k) where fmap f = runIdentity . traverse (Identity . f) instance Foldable (PatchMapWithMove k) where foldMap = foldMapDefault instance Traversable (PatchMapWithMove k) where traverse = _PatchMapWithMove . traverse . traverse instance FunctorWithIndex k (PatchMapWithMove k) instance FoldableWithIndex k (PatchMapWithMove k) instance TraversableWithIndex k (PatchMapWithMove k) where itraverse = itraversed . Indexed itraversed = _PatchMapWithMove .> itraversed <. traverse -- | Create a 'PatchMapWithMove', validating it patchMapWithMove :: Ord k => Map k (NodeInfo k v) -> Maybe (PatchMapWithMove k v) patchMapWithMove = fmap PatchMapWithMove' . PM.patchMapWithPatchingMove . coerce -- | Create a 'PatchMapWithMove' that inserts everything in the given 'Map' patchMapWithMoveInsertAll :: Map k v -> PatchMapWithMove k v patchMapWithMoveInsertAll = PatchMapWithMove' . PM.patchMapWithPatchingMoveInsertAll -- | Make a @'PatchMapWithMove' k v@ which has the effect of inserting or updating a value @v@ to the given key @k@, like 'Map.insert'. insertMapKey :: k -> v -> PatchMapWithMove k v insertMapKey k v = PatchMapWithMove' $ PM.insertMapKey k v -- |Make a @'PatchMapWithMove' k v@ which has the effect of moving the value from the first key @k@ to the second key @k@, equivalent to: -- -- @ -- 'Map.delete' src (maybe map ('Map.insert' dst) (Map.lookup src map)) -- @ moveMapKey :: Ord k => k -> k -> PatchMapWithMove k v moveMapKey src dst = PatchMapWithMove' $ PM.moveMapKey src dst -- |Make a @'PatchMapWithMove' k v@ which has the effect of swapping two keys in the mapping, equivalent to: -- -- @ -- let aMay = Map.lookup a map -- bMay = Map.lookup b map -- in maybe id (Map.insert a) (bMay <> aMay) -- . maybe id (Map.insert b) (aMay <> bMay) -- . Map.delete a . Map.delete b $ map -- @ swapMapKey :: Ord k => k -> k -> PatchMapWithMove k v swapMapKey src dst = PatchMapWithMove' $ PM.swapMapKey src dst -- |Make a @'PatchMapWithMove' k v@ which has the effect of deleting a key in the mapping, equivalent to 'Map.delete'. deleteMapKey :: k -> PatchMapWithMove k v deleteMapKey = PatchMapWithMove' . PM.deleteMapKey -- | Wrap a @'Map' k (NodeInfo k v)@ representing patch changes into a @'PatchMapWithMove' k v@, without checking any invariants. -- -- __Warning:__ when using this function, you must ensure that the invariants of 'PatchMapWithMove' are preserved; they will not be checked. unsafePatchMapWithMove :: Map k (NodeInfo k v) -> PatchMapWithMove k v unsafePatchMapWithMove = coerce PM.unsafePatchMapWithPatchingMove -- | Apply the insertions, deletions, and moves to a given 'Map' instance Ord k => Patch (PatchMapWithMove k v) where type PatchTarget (PatchMapWithMove k v) = Map k v apply (PatchMapWithMove' p) = apply p -- | Returns all the new elements that will be added to the 'Map'. patchMapWithMoveNewElements :: PatchMapWithMove k v -> [v] patchMapWithMoveNewElements = PM.patchMapWithPatchingMoveNewElements . unPatchMapWithMove' -- | Return a @'Map' k v@ with all the inserts/updates from the given @'PatchMapWithMove' k v@. patchMapWithMoveNewElementsMap :: PatchMapWithMove k v -> Map k v patchMapWithMoveNewElementsMap = PM.patchMapWithPatchingMoveNewElementsMap . unPatchMapWithMove' -- | Create a 'PatchMapWithMove' that, if applied to the given 'Map', will sort -- its values using the given ordering function. The set keys of the 'Map' is -- not changed. patchThatSortsMapWith :: Ord k => (v -> v -> Ordering) -> Map k v -> PatchMapWithMove k v patchThatSortsMapWith cmp = PatchMapWithMove' . PM.patchThatSortsMapWith cmp -- | Create a 'PatchMapWithMove' that, if applied to the first 'Map' provided, -- will produce a 'Map' with the same values as the second 'Map' but with the -- values sorted with the given ordering function. patchThatChangesAndSortsMapWith :: (Ord k, Ord v) => (v -> v -> Ordering) -> Map k v -> Map k v -> PatchMapWithMove k v patchThatChangesAndSortsMapWith cmp oldByIndex newByIndexUnsorted = patchThatChangesMap oldByIndex newByIndex where newList = Map.toList newByIndexUnsorted newByIndex = Map.fromList $ zip (fst <$> newList) $ sortBy cmp $ snd <$> newList -- | Create a 'PatchMapWithMove' that, if applied to the first 'Map' provided, -- will produce the second 'Map'. patchThatChangesMap :: (Ord k, Ord v) => Map k v -> Map k v -> PatchMapWithMove k v patchThatChangesMap oldByIndex newByIndex = PatchMapWithMove' $ PM.patchThatChangesMap oldByIndex newByIndex -- -- NodeInfo -- -- | Holds the information about each key: where its new value should come from, -- and where its old value should go to newtype NodeInfo k (v :: Type) = NodeInfo' { unNodeInfo' :: PM.NodeInfo k (Proxy v) } deriving instance (Show k, Show p) => Show (NodeInfo k p) deriving instance (Read k, Read p) => Read (NodeInfo k p) deriving instance (Eq k, Eq p) => Eq (NodeInfo k p) deriving instance (Ord k, Ord p) => Ord (NodeInfo k p) {-# COMPLETE NodeInfo #-} pattern NodeInfo :: To k -> From k v -> NodeInfo k v _nodeInfo_to :: NodeInfo k v -> To k _nodeInfo_from :: NodeInfo k v -> From k v pattern NodeInfo { _nodeInfo_to, _nodeInfo_from } = NodeInfo' PM.NodeInfo { PM._nodeInfo_to = _nodeInfo_to , PM._nodeInfo_from = Coerce _nodeInfo_from } _NodeInfo :: Iso (NodeInfo k0 v0) (NodeInfo k1 v1) (PM.NodeInfo k0 (Proxy v0)) (PM.NodeInfo k1 (Proxy v1)) _NodeInfo = iso unNodeInfo' NodeInfo' instance Functor (NodeInfo k) where fmap f = runIdentity . traverse (Identity . f) instance Foldable (NodeInfo k) where foldMap = foldMapDefault instance Traversable (NodeInfo k) where traverse = bitraverseNodeInfo pure bitraverseNodeInfo :: Applicative f => (k0 -> f k1) -> (v0 -> f v1) -> NodeInfo k0 v0 -> f (NodeInfo k1 v1) bitraverseNodeInfo fk fv = fmap NodeInfo' . PM.bitraverseNodeInfo fk (\ ~Proxy -> pure Proxy) fv . coerce -- | Change the 'From' value of a 'NodeInfo' nodeInfoMapFrom :: (From k v -> From k v) -> NodeInfo k v -> NodeInfo k v nodeInfoMapFrom f = coerce $ PM.nodeInfoMapFrom (unFrom' . f . From') -- | Change the 'From' value of a 'NodeInfo', using a 'Functor' (or -- 'Applicative', 'Monad', etc.) action to get the new value nodeInfoMapMFrom :: Functor f => (From k v -> f (From k v)) -> NodeInfo k v -> f (NodeInfo k v) nodeInfoMapMFrom f = fmap NodeInfo' . PM.nodeInfoMapMFrom (fmap unFrom' . f . From') . coerce -- | Set the 'To' field of a 'NodeInfo' nodeInfoSetTo :: To k -> NodeInfo k v -> NodeInfo k v nodeInfoSetTo = coerce . PM.nodeInfoSetTo -- -- From -- -- | Describe how a key's new value should be produced newtype From k (v :: Type) = From' { unFrom' :: PM.From k (Proxy v) } {-# COMPLETE From_Insert, From_Delete, From_Move #-} -- | Insert the given value here pattern From_Insert :: v -> From k v pattern From_Insert v = From' (PM.From_Insert v) -- | Delete the existing value, if any, from here pattern From_Delete :: From k v pattern From_Delete = From' PM.From_Delete -- | Move the value here from the given key pattern From_Move :: k -> From k v pattern From_Move k = From' (PM.From_Move k Proxy) bitraverseFrom :: Applicative f => (k0 -> f k1) -> (v0 -> f v1) -> From k0 v0 -> f (From k1 v1) bitraverseFrom fk fv = fmap From' . PM.bitraverseFrom fk (\ ~Proxy -> pure Proxy) fv . coerce makeWrapped ''PatchMapWithMove makeWrapped ''NodeInfo makeWrapped ''From