-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathMapWithMove.hs
More file actions
283 lines (255 loc) · 13.4 KB
/
MapWithMove.hs
File metadata and controls
283 lines (255 loc) · 13.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
-- | 'Patch'es on 'Map' that can insert, delete, and move values from one key to
-- another
module Data.Patch.MapWithMove where
import Data.Patch.Class
import Control.Arrow
import Control.Lens hiding (from, to)
import Control.Monad.Trans.State
import Data.Foldable
import Data.Function
import Data.List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Semigroup (Semigroup (..), (<>))
import qualified Data.Set as Set
import Data.These (These(..))
import Data.Tuple
-- | 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 = PatchMapWithMove
{ -- | Extract the internal representation of the 'PatchMapWithMove'
unPatchMapWithMove :: Map k (NodeInfo k v)
}
deriving ( Show, Read, Eq, Ord
, Functor, Foldable, Traversable
)
-- | Holds the information about each key: where its new value should come from,
-- and where its old value should go to
data NodeInfo k v = NodeInfo
{ _nodeInfo_from :: !(From k v)
-- ^ Where do we get the new value for this key?
, _nodeInfo_to :: !(To k)
-- ^ If the old value is being kept (i.e. moved rather than deleted or
-- replaced), where is it going?
}
deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
-- | Describe how a key's new value should be produced
data From k v
= From_Insert v -- ^ Insert the given value here
| From_Delete -- ^ Delete the existing value, if any, from here
| From_Move !k -- ^ Move the value here from the given key
deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
-- | Describe where a key's old value will go. If this is 'Just', that means
-- the key's old value will be moved to the given other key; if it is 'Nothing',
-- that means it will be deleted.
type To = Maybe
makeWrapped ''PatchMapWithMove
instance FunctorWithIndex k (PatchMapWithMove k)
instance FoldableWithIndex k (PatchMapWithMove k)
instance TraversableWithIndex k (PatchMapWithMove k) where
itraverse f (PatchMapWithMove x) = PatchMapWithMove <$> itraverse (traverse . f) x
-- | Create a 'PatchMapWithMove', validating it
patchMapWithMove :: Ord k => Map k (NodeInfo k v) -> Maybe (PatchMapWithMove k v)
patchMapWithMove m = if valid then Just $ PatchMapWithMove m else Nothing
where valid = forwardLinks == backwardLinks
forwardLinks = Map.mapMaybe _nodeInfo_to m
backwardLinks = Map.fromList $ catMaybes $ flip fmap (Map.toList m) $ \(to, v) ->
case _nodeInfo_from v of
From_Move from -> Just (from, to)
_ -> Nothing
-- | Create a 'PatchMapWithMove' that inserts everything in the given 'Map'
patchMapWithMoveInsertAll :: Map k v -> PatchMapWithMove k v
patchMapWithMoveInsertAll m = PatchMapWithMove $ flip fmap m $ \v -> NodeInfo
{ _nodeInfo_from = From_Insert v
, _nodeInfo_to = Nothing
}
-- | 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 . Map.singleton k $ NodeInfo (From_Insert v) Nothing
-- |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
| src == dst = mempty
| otherwise =
PatchMapWithMove $ Map.fromList
[ (dst, NodeInfo (From_Move src) Nothing)
, (src, NodeInfo From_Delete (Just 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 `mplus` aMay)
-- . maybe id (Map.insert b) (aMay `mplus` bMay)
-- . Map.delete a . Map.delete b $ map
-- @
swapMapKey :: Ord k => k -> k -> PatchMapWithMove k v
swapMapKey src dst
| src == dst = mempty
| otherwise =
PatchMapWithMove $ Map.fromList
[ (dst, NodeInfo (From_Move src) (Just src))
, (src, NodeInfo (From_Move dst) (Just 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 k = PatchMapWithMove . Map.singleton k $ NodeInfo From_Delete Nothing
-- | 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 = PatchMapWithMove
-- | 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) 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?
where insertions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
From_Insert v -> Just v
From_Move k -> Map.lookup k old
From_Delete -> Nothing
deletions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
From_Delete -> Just ()
_ -> Nothing
-- | Returns all the new elements that will be added to the 'Map'.
patchMapWithMoveNewElements :: PatchMapWithMove k v -> [v]
patchMapWithMoveNewElements = Map.elems . patchMapWithMoveNewElementsMap
-- | Return a @'Map' k v@ with all the inserts/updates from the given @'PatchMapWithMove' k v@.
patchMapWithMoveNewElementsMap :: PatchMapWithMove k v -> Map k v
patchMapWithMoveNewElementsMap (PatchMapWithMove p) = Map.mapMaybe f p
where f ni = case _nodeInfo_from ni of
From_Insert v -> Just v
From_Move _ -> Nothing
From_Delete -> Nothing
-- | 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 m = PatchMapWithMove $ Map.fromList $ catMaybes $ zipWith g unsorted sorted
where unsorted = Map.toList m
sorted = sortBy (cmp `on` snd) unsorted
f (to, _) (from, _) = if to == from then Nothing else
Just (from, to)
reverseMapping = Map.fromList $ catMaybes $ zipWith f unsorted sorted
g (to, _) (from, _) = if to == from then Nothing else
let Just movingTo = Map.lookup to reverseMapping
in Just (to, NodeInfo (From_Move from) $ Just movingTo)
-- | 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 :: forall k v. (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 = patch
where oldByValue = Map.fromListWith Set.union $ swap . first Set.singleton <$> Map.toList oldByIndex
(insertsAndMoves, unusedValuesByValue) = flip runState oldByValue $ do
let f k v = do
remainingValues <- get
let putRemainingKeys remainingKeys = put $ if Set.null remainingKeys
then Map.delete v remainingValues
else Map.insert v remainingKeys remainingValues
case Map.lookup v remainingValues of
Nothing -> return $ NodeInfo (From_Insert v) $ Just undefined -- There's no existing value we can take
Just fromKs ->
if k `Set.member` fromKs
then do
putRemainingKeys $ Set.delete k fromKs
return $ NodeInfo (From_Move k) $ Just undefined -- There's an existing value, and it's here, so no patch necessary
else do
(fromK, remainingKeys) <- return . fromJust $ Set.minView fromKs -- There's an existing value, but it's not here; move it here
putRemainingKeys remainingKeys
return $ NodeInfo (From_Move fromK) $ Just undefined
Map.traverseWithKey f newByIndex
unusedOldKeys = fold unusedValuesByValue
pointlessMove k = \case
From_Move k' | k == k' -> True
_ -> False
keyWasMoved k = if k `Map.member` oldByIndex && not (k `Set.member` unusedOldKeys)
then Just undefined
else Nothing
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
-- | Change the 'From' value of a 'NodeInfo'
nodeInfoMapFrom :: (From k v -> From k v) -> NodeInfo k v -> NodeInfo k v
nodeInfoMapFrom f ni = ni { _nodeInfo_from = f $ _nodeInfo_from ni }
-- | 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 ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _nodeInfo_from ni
-- | Set the 'To' field of a 'NodeInfo'
nodeInfoSetTo :: To k -> NodeInfo k v -> NodeInfo k v
nodeInfoSetTo to ni = ni { _nodeInfo_to = to }
-- |Helper data structure used for composing patches using the monoid instance.
data Fixup k v
= Fixup_Delete
| Fixup_Update (These (From k v) (To k))
-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
instance Ord k => Semigroup (PatchMapWithMove k v) where
PatchMapWithMove ma <> PatchMapWithMove mb = PatchMapWithMove m
where
connections = Map.toList $ Map.intersectionWithKey (\_ a b -> (_nodeInfo_to a, _nodeInfo_from b)) ma mb
h :: (k, (Maybe k, From k v)) -> [(k, Fixup k v)]
h (_, (mToAfter, editBefore)) = case (mToAfter, editBefore) of
(Just toAfter, From_Move fromBefore)
| fromBefore == toAfter
-> [(toAfter, Fixup_Delete)]
| otherwise
-> [ (toAfter, Fixup_Update (This editBefore))
, (fromBefore, Fixup_Update (That mToAfter))
]
(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
(Just toAfter, _) -> [(toAfter, Fixup_Update (This editBefore))]
(Nothing, _) -> []
mergeFixups _ Fixup_Delete Fixup_Delete = Fixup_Delete
mergeFixups _ (Fixup_Update a) (Fixup_Update b)
| This x <- a, That y <- b
= Fixup_Update $ These x y
| That y <- a, This x <- b
= Fixup_Update $ These x y
mergeFixups _ _ _ = error "PatchMapWithMove: incompatible fixups"
fixups = Map.fromListWithKey mergeFixups $ concatMap h connections
combineNodeInfos _ nia nib = NodeInfo
{ _nodeInfo_from = _nodeInfo_from nia
, _nodeInfo_to = _nodeInfo_to nib
}
applyFixup _ ni = \case
Fixup_Delete -> Nothing
Fixup_Update u -> Just $ NodeInfo
{ _nodeInfo_from = fromMaybe (_nodeInfo_from ni) $ getHere u
, _nodeInfo_to = fromMaybe (_nodeInfo_to ni) $ getThere u
}
m = Map.differenceWithKey applyFixup (Map.unionWithKey combineNodeInfos ma mb) fixups
getHere :: These a b -> Maybe a
getHere = \case
This a -> Just a
These a _ -> Just a
That _ -> Nothing
getThere :: These a b -> Maybe b
getThere = \case
This _ -> Nothing
These _ b -> Just b
That b -> Just b
--TODO: Figure out how to implement this in terms of PatchDMapWithMove rather than duplicating it here
-- |Compose patches having the same effect as applying the patches in turn: @'applyAlways' (p <> q) == 'applyAlways' p . 'applyAlways' q@
instance Ord k => Monoid (PatchMapWithMove k v) where
mempty = PatchMapWithMove mempty
mappend = (<>)