on 82c1cd1 help sign in
patch/src/Data/Patch/MapWithMove.hs
287 lines 14 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE DeriveTraversable #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE LambdaCase #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE PatternGuards #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE TemplateHaskell #-}
10{-# LANGUAGE TypeApplications #-}
11{-# LANGUAGE TypeFamilies #-}
13-- | 'Patch'es on 'Map' that can insert, delete, and move values from one key to
14-- another
15module Data.Patch.MapWithMove where
17import Data.Patch.Class
19import Control.Arrow
20import Control.Lens hiding (from, to)
21import Control.Monad.Trans.State
22import Data.Foldable
23import Data.Function
24import Data.List
25import Data.Map (Map)
26import qualified Data.Map as Map
27import Data.Maybe
28#if !MIN_VERSION_base(4,10,0)
29import Data.Semigroup (Semigroup (..), (<>))
30#endif
31import qualified Data.Set as Set
32import Data.These (These(..))
33import Data.Tuple
35-- | Patch a Map with additions, deletions, and moves. Invariant: If key @k1@
36-- is coming from @From_Move k2@, then key @k2@ should be going to @Just k1@,
37-- and vice versa. There should never be any unpaired From/To keys.
38newtype PatchMapWithMove k v = PatchMapWithMove
39 { -- | Extract the internal representation of the 'PatchMapWithMove'
40 unPatchMapWithMove :: Map k (NodeInfo k v)
41 }
42 deriving ( Show, Read, Eq, Ord
43 , Functor, Foldable, Traversable
44 )
46-- | Holds the information about each key: where its new value should come from,
47-- and where its old value should go to
48data NodeInfo k v = NodeInfo
49 { _nodeInfo_from :: !(From k v)
50 -- ^ Where do we get the new value for this key?
51 , _nodeInfo_to :: !(To k)
52 -- ^ If the old value is being kept (i.e. moved rather than deleted or
53 -- replaced), where is it going?
54 }
55 deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
57-- | Describe how a key's new value should be produced
58data From k v
59 = From_Insert v -- ^ Insert the given value here
60 | From_Delete -- ^ Delete the existing value, if any, from here
61 | From_Move !k -- ^ Move the value here from the given key
62 deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
64-- | Describe where a key's old value will go. If this is 'Just', that means
65-- the key's old value will be moved to the given other key; if it is 'Nothing',
66-- that means it will be deleted.
67type To = Maybe
69makeWrapped ''PatchMapWithMove
71instance FunctorWithIndex k (PatchMapWithMove k)
72instance FoldableWithIndex k (PatchMapWithMove k)
73instance TraversableWithIndex k (PatchMapWithMove k) where
74 itraverse = itraversed . Indexed
75 itraversed = _Wrapped .> itraversed <. traversed
77-- | Create a 'PatchMapWithMove', validating it
78patchMapWithMove :: Ord k => Map k (NodeInfo k v) -> Maybe (PatchMapWithMove k v)
79patchMapWithMove m = if valid then Just $ PatchMapWithMove m else Nothing
80 where valid = forwardLinks == backwardLinks
81 forwardLinks = Map.mapMaybe _nodeInfo_to m
82 backwardLinks = Map.fromList $ catMaybes $ flip fmap (Map.toList m) $ \(to, v) ->
83 case _nodeInfo_from v of
84 From_Move from -> Just (from, to)
85 _ -> Nothing
87-- | Create a 'PatchMapWithMove' that inserts everything in the given 'Map'
88patchMapWithMoveInsertAll :: Map k v -> PatchMapWithMove k v
89patchMapWithMoveInsertAll m = PatchMapWithMove $ flip fmap m $ \v -> NodeInfo
90 { _nodeInfo_from = From_Insert v
91 , _nodeInfo_to = Nothing
92 }
94-- | Make a @'PatchMapWithMove' k v@ which has the effect of inserting or updating a value @v@ to the given key @k@, like 'Map.insert'.
95insertMapKey :: k -> v -> PatchMapWithMove k v
96insertMapKey k v = PatchMapWithMove . Map.singleton k $ NodeInfo (From_Insert v) Nothing
98-- |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:
99--
100-- @
101-- 'Map.delete' src (maybe map ('Map.insert' dst) (Map.lookup src map))
102-- @
103moveMapKey :: Ord k => k -> k -> PatchMapWithMove k v
104moveMapKey src dst
105 | src == dst = mempty
106 | otherwise =
107 PatchMapWithMove $ Map.fromList
108 [ (dst, NodeInfo (From_Move src) Nothing)
109 , (src, NodeInfo From_Delete (Just dst))
110 ]
112-- |Make a @'PatchMapWithMove' k v@ which has the effect of swapping two keys in the mapping, equivalent to:
113--
114-- @
115-- let aMay = Map.lookup a map
116-- bMay = Map.lookup b map
117-- in maybe id (Map.insert a) (bMay `mplus` aMay)
118-- . maybe id (Map.insert b) (aMay `mplus` bMay)
119-- . Map.delete a . Map.delete b $ map
120-- @
121swapMapKey :: Ord k => k -> k -> PatchMapWithMove k v
122swapMapKey src dst
123 | src == dst = mempty
124 | otherwise =
125 PatchMapWithMove $ Map.fromList
126 [ (dst, NodeInfo (From_Move src) (Just src))
127 , (src, NodeInfo (From_Move dst) (Just dst))
128 ]
130-- |Make a @'PatchMapWithMove' k v@ which has the effect of deleting a key in the mapping, equivalent to 'Map.delete'.
131deleteMapKey :: k -> PatchMapWithMove k v
132deleteMapKey k = PatchMapWithMove . Map.singleton k $ NodeInfo From_Delete Nothing
134-- | Wrap a @'Map' k (NodeInfo k v)@ representing patch changes into a @'PatchMapWithMove' k v@, without checking any invariants.
135--
136-- __Warning:__ when using this function, you must ensure that the invariants of 'PatchMapWithMove' are preserved; they will not be checked.
137unsafePatchMapWithMove :: Map k (NodeInfo k v) -> PatchMapWithMove k v
138unsafePatchMapWithMove = PatchMapWithMove
140-- | Apply the insertions, deletions, and moves to a given 'Map'
141instance Ord k => Patch (PatchMapWithMove k v) where
142 type PatchTarget (PatchMapWithMove k v) = Map k v
143 apply (PatchMapWithMove p) old = Just $! insertions `Map.union` (old `Map.difference` deletions) --TODO: return Nothing sometimes --Note: the strict application here is critical to ensuring that incremental merges don't hold onto all their prerequisite events forever; can we make this more robust?
144 where insertions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
145 From_Insert v -> Just v
146 From_Move k -> Map.lookup k old
147 From_Delete -> Nothing
148 deletions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
149 From_Delete -> Just ()
150 _ -> Nothing
152-- | Returns all the new elements that will be added to the 'Map'.
153patchMapWithMoveNewElements :: PatchMapWithMove k v -> [v]
154patchMapWithMoveNewElements = Map.elems . patchMapWithMoveNewElementsMap
156-- | Return a @'Map' k v@ with all the inserts/updates from the given @'PatchMapWithMove' k v@.
157patchMapWithMoveNewElementsMap :: PatchMapWithMove k v -> Map k v
158patchMapWithMoveNewElementsMap (PatchMapWithMove p) = Map.mapMaybe f p
159 where f ni = case _nodeInfo_from ni of
160 From_Insert v -> Just v
161 From_Move _ -> Nothing
162 From_Delete -> Nothing
164-- | Create a 'PatchMapWithMove' that, if applied to the given 'Map', will sort
165-- its values using the given ordering function. The set keys of the 'Map' is
166-- not changed.
167patchThatSortsMapWith :: Ord k => (v -> v -> Ordering) -> Map k v -> PatchMapWithMove k v
168patchThatSortsMapWith cmp m = PatchMapWithMove $ Map.fromList $ catMaybes $ zipWith g unsorted sorted
169 where unsorted = Map.toList m
170 sorted = sortBy (cmp `on` snd) unsorted
171 f (to, _) (from, _) = if to == from then Nothing else
172 Just (from, to)
173 reverseMapping = Map.fromList $ catMaybes $ zipWith f unsorted sorted
174 g (to, _) (from, _) = if to == from then Nothing else
175 let Just movingTo = Map.lookup to reverseMapping
176 in Just (to, NodeInfo (From_Move from) $ Just movingTo)
178-- | Create a 'PatchMapWithMove' that, if applied to the first 'Map' provided,
179-- will produce a 'Map' with the same values as the second 'Map' but with the
180-- values sorted with the given ordering function.
181patchThatChangesAndSortsMapWith :: forall k v. (Ord k, Ord v) => (v -> v -> Ordering) -> Map k v -> Map k v -> PatchMapWithMove k v
182patchThatChangesAndSortsMapWith cmp oldByIndex newByIndexUnsorted = patchThatChangesMap oldByIndex newByIndex
183 where newList = Map.toList newByIndexUnsorted
184 newByIndex = Map.fromList $ zip (fst <$> newList) $ sortBy cmp $ snd <$> newList
186-- | Create a 'PatchMapWithMove' that, if applied to the first 'Map' provided,
187-- will produce the second 'Map'.
188patchThatChangesMap :: (Ord k, Ord v) => Map k v -> Map k v -> PatchMapWithMove k v
189patchThatChangesMap oldByIndex newByIndex = patch
190 where oldByValue = Map.fromListWith Set.union $ swap . first Set.singleton <$> Map.toList oldByIndex
191 (insertsAndMoves, unusedValuesByValue) = flip runState oldByValue $ do
192 let f k v = do
193 remainingValues <- get
194 let putRemainingKeys remainingKeys = put $ if Set.null remainingKeys
195 then Map.delete v remainingValues
196 else Map.insert v remainingKeys remainingValues
197 case Map.lookup v remainingValues of
198 Nothing -> return $ NodeInfo (From_Insert v) $ Just undefined -- There's no existing value we can take
199 Just fromKs ->
200 if k `Set.member` fromKs
201 then do
202 putRemainingKeys $ Set.delete k fromKs
203 return $ NodeInfo (From_Move k) $ Just undefined -- There's an existing value, and it's here, so no patch necessary
204 else do
205 (fromK, remainingKeys) <- return . fromJust $ Set.minView fromKs -- There's an existing value, but it's not here; move it here
206 putRemainingKeys remainingKeys
207 return $ NodeInfo (From_Move fromK) $ Just undefined
208 Map.traverseWithKey f newByIndex
209 unusedOldKeys = fold unusedValuesByValue
210 pointlessMove k = \case
211 From_Move k' | k == k' -> True
212 _ -> False
213 keyWasMoved k = if k `Map.member` oldByIndex && not (k `Set.member` unusedOldKeys)
214 then Just undefined
215 else Nothing
216 patch = unsafePatchMapWithMove $ Map.filterWithKey (\k -> not . pointlessMove k . _nodeInfo_from) $ Map.mergeWithKey (\k a _ -> Just $ nodeInfoSetTo (keyWasMoved k) a) (Map.mapWithKey $ \k -> nodeInfoSetTo $ keyWasMoved k) (Map.mapWithKey $ \k _ -> NodeInfo From_Delete $ keyWasMoved k) insertsAndMoves oldByIndex
218-- | Change the 'From' value of a 'NodeInfo'
219nodeInfoMapFrom :: (From k v -> From k v) -> NodeInfo k v -> NodeInfo k v
220nodeInfoMapFrom f ni = ni { _nodeInfo_from = f $ _nodeInfo_from ni }
222-- | Change the 'From' value of a 'NodeInfo', using a 'Functor' (or
223-- 'Applicative', 'Monad', etc.) action to get the new value
224nodeInfoMapMFrom :: Functor f => (From k v -> f (From k v)) -> NodeInfo k v -> f (NodeInfo k v)
225nodeInfoMapMFrom f ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _nodeInfo_from ni
227-- | Set the 'To' field of a 'NodeInfo'
228nodeInfoSetTo :: To k -> NodeInfo k v -> NodeInfo k v
229nodeInfoSetTo to ni = ni { _nodeInfo_to = to }
231-- |Helper data structure used for composing patches using the monoid instance.
232data Fixup k v
233 = Fixup_Delete
234 | Fixup_Update (These (From k v) (To k))
236-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
237instance Ord k => Semigroup (PatchMapWithMove k v) where
238 PatchMapWithMove ma <> PatchMapWithMove mb = PatchMapWithMove m
239 where
240 connections = Map.toList $ Map.intersectionWithKey (\_ a b -> (_nodeInfo_to a, _nodeInfo_from b)) ma mb
241 h :: (k, (Maybe k, From k v)) -> [(k, Fixup k v)]
242 h (_, (mToAfter, editBefore)) = case (mToAfter, editBefore) of
243 (Just toAfter, From_Move fromBefore)
244 | fromBefore == toAfter
245 -> [(toAfter, Fixup_Delete)]
246 | otherwise
247 -> [ (toAfter, Fixup_Update (This editBefore))
248 , (fromBefore, Fixup_Update (That mToAfter))
249 ]
250 (Nothing, From_Move fromBefore) -> [(fromBefore, Fixup_Update (That mToAfter))] -- The item is destroyed in the second patch, so indicate that it is destroyed in the source map
251 (Just toAfter, _) -> [(toAfter, Fixup_Update (This editBefore))]
252 (Nothing, _) -> []
253 mergeFixups _ Fixup_Delete Fixup_Delete = Fixup_Delete
254 mergeFixups _ (Fixup_Update a) (Fixup_Update b)
255 | This x <- a, That y <- b
256 = Fixup_Update $ These x y
257 | That y <- a, This x <- b
258 = Fixup_Update $ These x y
259 mergeFixups _ _ _ = error "PatchMapWithMove: incompatible fixups"
260 fixups = Map.fromListWithKey mergeFixups $ concatMap h connections
261 combineNodeInfos _ nia nib = NodeInfo
262 { _nodeInfo_from = _nodeInfo_from nia
263 , _nodeInfo_to = _nodeInfo_to nib
264 }
265 applyFixup _ ni = \case
266 Fixup_Delete -> Nothing
267 Fixup_Update u -> Just $ NodeInfo
268 { _nodeInfo_from = fromMaybe (_nodeInfo_from ni) $ getHere u
269 , _nodeInfo_to = fromMaybe (_nodeInfo_to ni) $ getThere u
270 }
271 m = Map.differenceWithKey applyFixup (Map.unionWithKey combineNodeInfos ma mb) fixups
272 getHere :: These a b -> Maybe a
273 getHere = \case
274 This a -> Just a
275 These a _ -> Just a
276 That _ -> Nothing
277 getThere :: These a b -> Maybe b
278 getThere = \case
279 This _ -> Nothing
280 These _ b -> Just b
281 That b -> Just b
283--TODO: Figure out how to implement this in terms of PatchDMapWithMove rather than duplicating it here
284-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
285instance Ord k => Monoid (PatchMapWithMove k v) where
286 mempty = PatchMapWithMove mempty
287 mappend = (<>)