on develop help sign in
Finish *WithMove patch work, and reorganize Reflex.Patch.* modules
commite8b2d71cf87d670a354699a6b24a26db51cfc191 authorRyan Trinkle <ryan.trinkle@gmail.com> dateSun 16 Apr 2017, 01:27 (10 years ago) parent4defc95
10 files changed+627−536
reflex.cabal+5 src/Data/Functor/Misc.hs+38−2 src/Reflex/Dynamic.hs+9 src/Reflex/DynamicWriter.hs+10−12 src/Reflex/Patch.hs+9−522 src/Reflex/Patch/Class.hs+20 src/Reflex/Patch/DMap.hs+57 src/Reflex/Patch/DMapWithMove.hs+275 src/Reflex/Patch/Map.hs+48 src/Reflex/Patch/MapWithMove.hs+156
reflex.cabal+5
@@ -75,6 +75,11 @@ library
7575 Reflex.FunctorMaybe,
7676 Reflex.Host.Class,
7777 Reflex.Patch,
78+ Reflex.Patch.Class,
79+ Reflex.Patch.DMap,
80+ Reflex.Patch.DMapWithMove,
81+ Reflex.Patch.Map,
82+ Reflex.Patch.MapWithMove,
7883 Reflex.TriggerEvent.Base,
7984 Reflex.TriggerEvent.Class,
8085 Reflex.PerformEvent.Base,
src/Data/Functor/Misc.hs+38−2
@@ -1,5 +1,6 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE DeriveFunctor #-}
34{-# LANGUAGE FlexibleContexts #-}
45{-# LANGUAGE FlexibleInstances #-}
56{-# LANGUAGE GADTs #-}
@@ -41,6 +42,7 @@ module Data.Functor.Misc
4142 , unwrapDMap
4243 , unwrapDMapMaybe
4344 , extractFunctorDMap
45+ , ComposeMaybe (..)
4446 ) where
4547
4648import Control.Applicative (Applicative, (<$>))
@@ -52,6 +54,7 @@ import Data.GADT.Compare
5254import Data.GADT.Show
5355import Data.Map (Map)
5456import qualified Data.Map as Map
57+import Data.Maybe
5558import Data.Some (Some)
5659import qualified Data.Some as Some
5760import Data.These
@@ -89,6 +92,12 @@ instance Ord k => GCompare (Const2 k v) where
8992 gcompareToOrd (Const2 _) = id
9093 gcompare (Const2 a) (Const2 b) = strengthenOrdering $ compare a b
9194
95+instance {-# INCOHERENT #-} (Eq k, Eq v) => EqTag (Const2 k v) Identity where
96+ eqTagToEq (Const2 _) _ = id
97+
98+instance {-# INCOHERENT #-} Eq k => EqTag (Const2 k v) (Const2 k v) where
99+ eqTagToEq (Const2 _) _ = id
100+
92101-- | Convert a 'DMap' to a regular 'Map'
93102dmapToMap :: DMap (Const2 k v) Identity -> Map k v
94103dmapToMap = Map.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> (k, v)) . DMap.toAscList
@@ -124,7 +133,7 @@ deriving instance Show (f a) => Show (WrapArg g f (g' a))
124133deriving instance Read (f a) => Read (WrapArg g f (g a))
125134
126135instance GEq f => GEq (WrapArg g f) where
127− geqToEq (WrapArg f) r = geqToEq f r
136+ geqToEq (WrapArg f) = geqToEq f
128137 geqUnify (WrapArg a) (WrapArg b) = geqUnify a b
129138
130139instance GCompare f => GCompare (WrapArg g f) where
@@ -180,6 +189,16 @@ deriving instance Show (EitherTag l r a)
180189deriving instance Eq (EitherTag l r a)
181190deriving instance Ord (EitherTag l r a)
182191
192+instance {-# INCOHERENT #-} (Show (f l), Show (f r)) => ShowTag (EitherTag l r) f where
193+ showTagToShow e _ = case e of
194+ LeftTag -> id
195+ RightTag -> id
196+
197+instance {-# INCOHERENT #-} (Eq (f l), Eq (f r)) => EqTag (EitherTag l r) f where
198+ eqTagToEq e _ = case e of
199+ LeftTag -> id
200+ RightTag -> id
201+
183202instance GEq (EitherTag l r) where
184203 geqToEq _ = id
185204 geqUnify LeftTag LeftTag _ same = same
@@ -218,12 +237,29 @@ dsumToEither = \case
218237 (LeftTag :=> Identity a) -> Left a
219238 (RightTag :=> Identity b) -> Right b
220239
240+--------------------------------------------------------------------------------
241+-- ComposeMaybe
242+--------------------------------------------------------------------------------
243+
244+-- | We can't use @Compose Maybe@ instead of 'ComposeMaybe', because that would
245+-- make the 'f' parameter have a nominal type role. We need f to be
246+-- representational so that we can use safe 'coerce'.
247+newtype ComposeMaybe f a = ComposeMaybe { getComposeMaybe :: Maybe (f a) } deriving (Show, Eq, Ord)
248+
249+instance EqTag f g => EqTag f (ComposeMaybe g) where
250+ eqTagToEq f _ = eqTagToEq f (Proxy :: Proxy g)
251+
252+deriving instance Functor f => Functor (ComposeMaybe f)
253+
254+instance {-# INCOHERENT #-} GShow k => ShowTag k (ComposeMaybe k) where
255+ showTagToShow k _ r = gshowToShow k (showTagToShow k (Proxy :: Proxy k) r)
256+
221257--------------------------------------------------------------------------------
222258-- Deprecated functions
223259--------------------------------------------------------------------------------
224260
225261{-# INLINE sequenceDmap #-}
226−{-# DEPRECATED sequenceDmap "Use 'Data.Dependent.Map.traverseWithKey (\\_ t -> fmap Identity t)' instead" #-}
262+{-# DEPRECATED sequenceDmap "Use 'Data.Dependent.Map.traverseWithKey (\\_ -> fmap Identity)' instead" #-}
227263-- | Run the actions contained in the 'DMap'
228264sequenceDmap :: Applicative t => DMap f t -> t (DMap f Identity)
229265sequenceDmap = DMap.traverseWithKey $ \_ t -> Identity <$> t
src/Reflex/Dynamic.hs+9
@@ -9,6 +9,7 @@
99{-# LANGUAGE PolyKinds #-}
1010{-# LANGUAGE RankNTypes #-}
1111{-# LANGUAGE ScopedTypeVariables #-}
12+{-# LANGUAGE StandaloneDeriving #-}
1213{-# LANGUAGE TypeFamilies #-}
1314{-# LANGUAGE TypeOperators #-}
1415{-# LANGUAGE UndecidableInstances #-}
@@ -333,12 +334,17 @@ data FHList f l where
333334 FHCons :: f e -> FHList f l -> FHList f (e ': l)
334335
335336instance GEq (HListPtr l) where
337+ geqToEq _ = id
338+ geqUnify HHeadPtr HHeadPtr _ same = same
339+ geqUnify (HTailPtr a) (HTailPtr b) different same = geqUnify a b different same
340+ geqUnify _ _ different _ = different
336341 HHeadPtr `geq` HHeadPtr = Just Refl
337342 HHeadPtr `geq` HTailPtr _ = Nothing
338343 HTailPtr _ `geq` HHeadPtr = Nothing
339344 HTailPtr a `geq` HTailPtr b = a `geq` b
340345
341346instance GCompare (HListPtr l) where -- Warning: This ordering can't change, dmapTo*HList will break
347+ gcompareToOrd _ = id
342348 HHeadPtr `gcompare` HHeadPtr = GEQ
343349 HHeadPtr `gcompare` HTailPtr _ = GLT
344350 HTailPtr _ `gcompare` HHeadPtr = GGT
@@ -349,6 +355,9 @@ data HListPtr l a where
349355 HHeadPtr :: HListPtr (h ': t) h
350356 HTailPtr :: HListPtr t a -> HListPtr (h ': t) a
351357
358+deriving instance Eq (HListPtr l a)
359+deriving instance Ord (HListPtr l a)
360+
352361fhlistToDMap :: forall (f :: * -> *) l. FHList f l -> DMap (HListPtr l) f
353362fhlistToDMap = DMap.fromList . go
354363 where go :: forall l'. FHList f l' -> [DSum (HListPtr l') f]
src/Reflex/DynamicWriter.hs+10−12
@@ -38,6 +38,7 @@ import Data.Some (Some)
3838import Data.These
3939import Reflex.Class
4040import Reflex.Host.Class
41+import qualified Reflex.Patch.MapWithMove as MapWithMove
4142import Reflex.PerformEvent.Class
4243import Reflex.PostBuild.Class
4344import Reflex.Requester.Class
@@ -54,29 +55,26 @@ mergeDynIncremental a = unsafeBuildIncremental (mapM (sample . current) =<< samp
5455 where changedValues = fmap (PatchMap . fmap Just) $ mergeMapIncremental $ mapIncrementalMapValues updated a
5556 addedAndRemovedValues = flip pushAlways (updatedIncremental a) $ \(PatchMap m) -> PatchMap <$> mapM (mapM (sample . current)) m
5657
57−mergeDynIncrementalWithMove :: (Reflex t, Ord k) => Incremental t (PatchMapWithMove k (Dynamic t v)) -> Incremental t (PatchMapWithMove k v)
58+mergeDynIncrementalWithMove :: forall t k v. (Reflex t, Ord k) => Incremental t (PatchMapWithMove k (Dynamic t v)) -> Incremental t (PatchMapWithMove k v)
5859mergeDynIncrementalWithMove a = unsafeBuildIncremental (mapM (sample . current) =<< sample (currentIncremental a)) $ alignWith f addedAndRemovedValues changedValues
5960 where changedValues = mergeMapIncrementalWithMove $ mapIncrementalMapValues updated a
6061 addedAndRemovedValues = flip pushAlways (updatedIncremental a) $ fmap unsafePatchMapWithMove . mapM (mapM (sample . current)) . unPatchMapWithMove
62+ f :: These (PatchMapWithMove k v) (Map k v) -> PatchMapWithMove k v
6163 f x = unsafePatchMapWithMove $
6264 let (p, changed) = case x of
6365 This p_ -> (unPatchMapWithMove p_, mempty)
6466 That c -> (mempty, c)
6567 These p_ c -> (unPatchMapWithMove p_, c)
66− (pWithNewVals, noLongerMoved) = flip runState [] $ forM p $ \case
67− MapEdit_Insert moved v -> return $ MapEdit_Insert moved v
68− MapEdit_Delete moved -> return $ MapEdit_Delete moved
69− MapEdit_Move moved k -> case Map.lookup k changed of
70− Nothing -> return $ MapEdit_Move moved k
68+ (pWithNewVals, noLongerMoved) = flip runState [] $ forM p $ MapWithMove.nodeInfoMapMFrom $ \case
69+ MapWithMove.From_Insert v -> return $ MapWithMove.From_Insert v
70+ MapWithMove.From_Delete -> return $ MapWithMove.From_Delete
71+ MapWithMove.From_Move k -> case Map.lookup k changed of
72+ Nothing -> return $ MapWithMove.From_Move k
7173 Just v -> do
7274 modify (k:)
73− return $ MapEdit_Insert moved v
75+ return $ MapWithMove.From_Insert v
7476 noLongerMovedMap = Map.fromList $ fmap (\k -> (k, ())) noLongerMoved
75− removeMovedFlag = \case --TODO: Check if moved was true before, in each case?
76− MapEdit_Insert _ v -> MapEdit_Insert False v
77− MapEdit_Delete _ -> MapEdit_Delete False
78− MapEdit_Move _ k -> MapEdit_Move False k
79− in Map.differenceWith (\e _ -> Just $ removeMovedFlag e) pWithNewVals noLongerMovedMap --TODO: Check if any in the second map are not covered?
77+ in Map.differenceWith (\e _ -> Just $ MapWithMove.nodeInfoSetTo Nothing e) pWithNewVals noLongerMovedMap --TODO: Check if any in the second map are not covered?
8078
8179-- | A basic implementation of 'MonadDynamicWriter'.
8280newtype DynamicWriterT t w m a = DynamicWriterT { unDynamicWriterT :: StateT [Dynamic t w] m a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadHold t, MonadSample t, MonadAsyncException, MonadException) -- The list is kept in reverse order
src/Reflex/Patch.hs+9−522
@@ -1,181 +1,18 @@
11-- | This module defines the 'Patch' class, which is used by Reflex to manage
22-- changes to 'Reflex.Class.Incremental' values.
3−{-# LANGUAGE CPP #-}
4−{-# LANGUAGE DeriveFoldable #-}
5−{-# LANGUAGE DeriveFunctor #-}
6−{-# LANGUAGE DeriveTraversable #-}
7−{-# LANGUAGE FlexibleContexts #-}
8−{-# LANGUAGE FlexibleInstances #-}
9−{-# LANGUAGE GADTs #-}
10−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
11−{-# LANGUAGE LambdaCase #-}
12−{-# LANGUAGE MultiParamTypeClasses #-}
13−{-# LANGUAGE PolyKinds #-}
14−{-# LANGUAGE Rank2Types #-}
15−{-# LANGUAGE ScopedTypeVariables #-}
16−{-# LANGUAGE StandaloneDeriving #-}
173{-# LANGUAGE TypeFamilies #-}
18−{-# LANGUAGE UndecidableInstances #-}
194module Reflex.Patch
20−{- ( Patch (..)
21− , PatchDMap (..)
22− , mapPatchDMap
23− , traversePatchDMap
24− , traversePatchDMapWithKey
25− , weakenPatchDMapWith
26− , patchDMapToPatchMapWith
27− , const2PatchDMapWith
28− , ComposeMaybe (..)
29− , PatchMap (..)
30− , patchMapNewElements
31− , Group (..)
32− , Additive
33− , AdditivePatch (..)
34− , PatchMapWithMove
35− , unPatchMapWithMove
36− , unsafePatchMapWithMove
37− , patchMapWithMoveNewElements
38− , MapEdit (..)
39− , mapEditMoved
40− , mapEditSetMoved
41− , PatchDMapWithMove
42−-- , unPatchDMapWithMove
43− , unsafePatchDMapWithMove
44− , patchThatSortsMapWith
45− , patchThatChangesAndSortsMapWith
46− , mapPatchDMapWithMove
47− , traversePatchDMapWithMove
48− , traversePatchDMapWithMoveWithKey
49− , weakenPatchDMapWithMoveWith
50− , patchDMapWithMoveToPatchMapWithMoveWith
51− , const2PatchDMapWithMoveWith
52− , DMapEdit (..)
53− , dmapEditMoved
54− -- * Test functions
55− , validPatchMapWithMove
56− , knownValidPatchMapWithMove
57− , knownInvalidPatchMapWithMove
58− ) -} where
5+ ( module Reflex.Patch
6+ , module X
7+ ) where
598
60−import Control.Arrow
61−import Control.Monad.Identity
62−import Control.Monad.State
63−import Data.Dependent.Map (DMap, DSum (..), GCompare (..))
64−import qualified Data.Dependent.Map as DMap
65−import Data.Dependent.Sum (ShowTag (..), ReadTag (..), EqTag (..), OrdTag (..))
66−import Data.Foldable
67−import Data.Function
68−import Data.Functor.Constant
69−import Data.Functor.Misc
70−import Data.GADT.Compare (GEq (..))
71−import Data.GADT.Show (GShow (..))
72−import Data.List
73−import Data.Map (Map)
74−import qualified Data.Map as Map
75−import Data.Maybe
76−import Data.Semigroup (Semigroup (..), stimesIdempotentMonoid, (<>))
77−import qualified Data.Set as Set
78−import Data.Some (Some)
79−import qualified Data.Some as Some
80−import Data.Tuple
81−import Data.Typeable (Proxy (..))
9+import Reflex.Patch.Class as X
10+import Reflex.Patch.DMap as X
11+import Reflex.Patch.DMapWithMove as X (PatchDMapWithMove, const2PatchDMapWithMoveWith, patchDMapWithMoveToPatchMapWithMoveWith, mapPatchDMapWithMove, weakenPatchDMapWithMoveWith, traversePatchDMapWithMoveWithKey, unsafePatchDMapWithMove, unPatchDMapWithMove)
12+import Reflex.Patch.Map as X
13+import Reflex.Patch.MapWithMove as X (PatchMapWithMove, patchMapWithMoveNewElements, unsafePatchMapWithMove, unPatchMapWithMove)
8214
83−-- | A 'Patch' type represents a kind of change made to a datastructure.
84−class Patch p where
85− type PatchTarget p :: *
86− -- | Apply the patch @p a@ to the value @a@. If no change is needed, return
87− -- 'Nothing'.
88− apply :: p -> PatchTarget p -> Maybe (PatchTarget p)
89−
90−applyAlways :: Patch p => p -> PatchTarget p -> PatchTarget p
91−applyAlways p t = fromMaybe t $ apply p t
92−
93−-- | We can't use @Compose Maybe@ instead of 'ComposeMaybe', because that would
94−-- make the 'f' parameter have a nominal type role. We need f to be
95−-- representational so that we can use safe 'coerce'.
96−newtype ComposeMaybe f a = ComposeMaybe { getComposeMaybe :: Maybe (f a) } deriving (Show, Eq)
97−
98−instance EqTag f g => EqTag f (ComposeMaybe g) where
99− eqTagToEq f p r = eqTagToEq f (Proxy :: Proxy g) r
100−
101−deriving instance Functor f => Functor (ComposeMaybe f)
102−
103−instance Patch (Identity a) where
104− type PatchTarget (Identity a) = a
105− apply (Identity a) _ = Just a
106−
107−-- | A set of changes to a 'DMap'. Any element may be inserted/updated or
108−-- deleted.
109−newtype PatchDMap k v = PatchDMap (DMap k (ComposeMaybe v))
110−
111−deriving instance GCompare k => Semigroup (PatchDMap k v)
112−
113−deriving instance GCompare k => Monoid (PatchDMap k v)
114−
115−instance GCompare k => Patch (PatchDMap k v) where
116− type PatchTarget (PatchDMap k v) = DMap k v
117− apply (PatchDMap diff) old = Just $! insertions `DMap.union` (old `DMap.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?
118− where insertions = DMap.mapMaybeWithKey (const $ getComposeMaybe) diff
119− deletions = DMap.mapMaybeWithKey (const $ nothingToJust . getComposeMaybe) diff
120− nothingToJust = \case
121− Nothing -> Just $ Constant ()
122− Just _ -> Nothing
123−
124−mapPatchDMap :: (forall a. v a -> v' a) -> PatchDMap k v -> PatchDMap k v'
125−mapPatchDMap f (PatchDMap p) = PatchDMap $ DMap.map (ComposeMaybe . fmap f . getComposeMaybe) p
126−
127−traversePatchDMap :: Applicative m => (forall a. v a -> m (v' a)) -> PatchDMap k v -> m (PatchDMap k v')
128−traversePatchDMap f = traversePatchDMapWithKey $ const f
129−
130−traversePatchDMapWithKey :: Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMap k v -> m (PatchDMap k v')
131−traversePatchDMapWithKey f (PatchDMap p) = PatchDMap <$> DMap.traverseWithKey (\k (ComposeMaybe v) -> ComposeMaybe <$> traverse (f k) v) p
132−
133−weakenPatchDMapWith :: (forall a. v a -> v') -> PatchDMap k v -> PatchMap (Some k) v'
134−weakenPatchDMapWith f (PatchDMap p) = PatchMap $ weakenDMapWith (fmap f . getComposeMaybe) p
135−
136−patchDMapToPatchMapWith :: (f v -> v') -> PatchDMap (Const2 k v) f -> PatchMap k v'
137−patchDMapToPatchMapWith f (PatchDMap p) = PatchMap $ dmapToMapWith (fmap f . getComposeMaybe) p
138−
139−const2PatchDMapWith :: forall k v f a. (v -> f a) -> PatchMap k v -> PatchDMap (Const2 k a) f
140−const2PatchDMapWith f (PatchMap p) = PatchDMap $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
141− where g :: (k, Maybe v) -> DSum (Const2 k a) (ComposeMaybe f)
142− g (k, e) = Const2 k :=> ComposeMaybe (f <$> e)
143−
144−-- | A set of changes to a 'Map'. Any element may be inserted/updated or
145−-- deleted.
146−newtype PatchMap k v = PatchMap { unPatchMap :: Map k (Maybe v) }
147−
148−instance Ord k => Patch (PatchMap k v) where
149− type PatchTarget (PatchMap k v) = Map k v
150− apply (PatchMap 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?
151− where insertions = Map.mapMaybeWithKey (const id) p
152− deletions = Map.mapMaybeWithKey (const nothingToJust) p
153− nothingToJust = \case
154− Nothing -> Just ()
155− Just _ -> Nothing
156−
157−instance Ord k => Semigroup (PatchMap k v) where
158− PatchMap a <> PatchMap b = PatchMap $ a `mappend` b --TODO: Add a semigroup instance for Map
159− -- PatchMap is idempotent, so stimes n is id for every n
160−#if MIN_VERSION_semigroups(0,17,0)
161− stimes = stimesIdempotentMonoid
162−#else
163− times1p n x = case compare n 0 of
164− LT -> error "stimesIdempotentMonoid: negative multiplier"
165− EQ -> mempty
166− GT -> x
167−#endif
168−
169−instance Ord k => Monoid (PatchMap k v) where
170− mempty = PatchMap mempty
171− mappend = (<>)
172−
173−instance Functor (PatchMap k) where
174− fmap f = PatchMap . fmap (fmap f) . unPatchMap
175−
176−-- | Returns all the new elements that will be added to the 'Map'
177−patchMapNewElements :: PatchMap k v -> [v]
178−patchMapNewElements (PatchMap p) = catMaybes $ Map.elems p
15+import Data.Semigroup (Semigroup (..), (<>))
17916
18017---- Patches based on commutative groups
18118
@@ -194,353 +31,3 @@ newtype AdditivePatch p = AdditivePatch { unAdditivePatch :: p }
19431instance Additive p => Patch (AdditivePatch p) where
19532 type PatchTarget (AdditivePatch p) = p
19633 apply (AdditivePatch p) q = Just $ p <> q
197−
198−data MapEdit k v
199− = MapEdit_Insert (Maybe k) v -- ^ Insert the given value here
200− | MapEdit_Delete (Maybe k) -- ^ Delete the existing value, if any, from here
201− | MapEdit_Move (Maybe k) k -- ^ Move the value here from the given key
202− deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
203−
204−mapEditMoved :: MapEdit k v -> Bool
205−mapEditMoved = isJust . \case
206− MapEdit_Insert moved _ -> moved
207− MapEdit_Delete moved -> moved
208− MapEdit_Move moved _ -> moved
209−
210−mapEditSetMoved :: Maybe k -> MapEdit k v -> MapEdit k v
211−mapEditSetMoved moved = \case
212− MapEdit_Insert _ v -> MapEdit_Insert moved v
213− MapEdit_Delete _ -> MapEdit_Delete moved
214− MapEdit_Move _ k -> MapEdit_Move moved k
215−
216−-- | Patch a DMap with additions, deletions, and moves.
217−-- Invariants:
218−-- * The same source key may not be moved to two destination keys.
219−-- * Any key that is the source of a Move must also be edited
220−-- (otherwise, the Move would duplicate it)
221−data PatchMapWithMove k v = PatchMapWithMove (Map k (MapEdit k v)) deriving (Show, Eq, Ord, Functor, Foldable, Traversable)
222−
223−unPatchMapWithMove :: PatchMapWithMove k v -> Map k (MapEdit k v)
224−unPatchMapWithMove (PatchMapWithMove p) = p
225−
226−-- | Warning: when using this function, you must ensure that the invariants of
227−-- 'PatchMapWithMove' are preserved; they will not be checked.
228−unsafePatchMapWithMove :: Map k (MapEdit k v) -> PatchMapWithMove k v
229−unsafePatchMapWithMove = PatchMapWithMove
230−
231−instance Ord k => Patch (PatchMapWithMove k v) where
232− type PatchTarget (PatchMapWithMove k v) = Map k v
233− 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?
234− where insertions = flip Map.mapMaybeWithKey p $ const $ \case
235− MapEdit_Insert _ v -> Just v
236− MapEdit_Move _ k -> Map.lookup k old
237− MapEdit_Delete _ -> Nothing
238− deletions = flip Map.mapMaybeWithKey p $ const $ \case
239− MapEdit_Delete _ -> Just ()
240− _ -> Nothing
241−
242−-- | Returns all the new elements that will be added to the 'Map'
243−patchMapWithMoveNewElements :: PatchMapWithMove k v -> [v]
244−patchMapWithMoveNewElements (PatchMapWithMove p) = catMaybes $ flip fmap (Map.elems p) $ \case
245− MapEdit_Insert _ v -> Just v
246− MapEdit_Move _ _ -> Nothing
247− MapEdit_Delete _ -> Nothing
248−
249−validPatchMapWithMove :: Ord k => PatchMapWithMove k v -> Bool
250−validPatchMapWithMove (PatchMapWithMove p) =
251− let moveSources = catMaybes $ flip fmap (Map.elems p) $ \case
252− MapEdit_Move _ k -> Just k
253− _ -> Nothing
254− in and
255− [ -- All Moves must have unique source keys - a single source key can't be moved to two destinations
256− all (==1) $ Map.fromListWith (+) $ fmap (\k -> (k, 1 :: Integer )) moveSources
257− , -- All Move source keys must also be patched - since the value's being moved elsewhere, the original must either be Deleted or replaced (with Insert or Move)
258− all (isJust . flip Map.lookup p) moveSources
259− ]
260−
261−knownValidPatchMapWithMove :: [PatchMapWithMove Int Int]
262−knownValidPatchMapWithMove =
263− [ PatchMapWithMove mempty
264− , PatchMapWithMove $ Map.fromList
265− [ (1, MapEdit_Move (Just 1) 2)
266− , (2, MapEdit_Move (Just 2) 1)
267− ]
268− ]
269−
270−knownInvalidPatchMapWithMove :: [PatchMapWithMove Int Int]
271−knownInvalidPatchMapWithMove =
272− [ -- Invalid because we move 2 to 1, but don't specify what to do with 2
273− PatchMapWithMove $ Map.singleton 1 $ MapEdit_Move Nothing 2
274− , -- Invalid because we have multiple moves from 3
275− PatchMapWithMove $ Map.fromList
276− [ (1, MapEdit_Move Nothing 3)
277− , (2, MapEdit_Move Nothing 3)
278− , (3, MapEdit_Delete (Just 1))
279− ]
280− ]
281−
282−patchThatSortsMapWith :: Ord k => (v -> v -> Ordering) -> Map k v -> PatchMapWithMove k v
283−patchThatSortsMapWith cmp m = PatchMapWithMove $ Map.fromList $ catMaybes $ zipWith g unsorted sorted
284− where unsorted = Map.toList m
285− sorted = sortBy (cmp `on` snd) unsorted
286− f (to, _) (from, _) = if to == from then Nothing else
287− Just (from, to)
288− reverseMapping = Map.fromList $ catMaybes $ zipWith f unsorted sorted
289− g (to, _) (from, _) = if to == from then Nothing else
290− let Just movingTo = Map.lookup from reverseMapping
291− in Just (to, MapEdit_Move (Just movingTo) from)
292−
293−patchThatChangesAndSortsMapWith :: (Ord k, Ord v) => (v -> v -> Ordering) -> Map k v -> Map k v -> PatchMapWithMove k v
294−patchThatChangesAndSortsMapWith cmp oldByIndex newByIndexUnsorted = patch
295− where oldByValue = Map.fromListWith Set.union $ swap . first Set.singleton <$> Map.toList oldByIndex
296− newList = Map.toList newByIndexUnsorted
297− newByIndex = Map.fromList $ zip (fst <$> newList) $ sortBy cmp $ snd <$> newList
298− (insertsAndMoves, unusedValuesByValue) = flip runState oldByValue $ do
299− let f k v = do
300− remainingValues <- get
301− let putRemainingKeys remainingKeys = put $ if Set.null remainingKeys
302− then Map.delete v remainingValues
303− else Map.insert v remainingKeys remainingValues
304− case Map.lookup v remainingValues of
305− Nothing -> return $ MapEdit_Insert (Just undefined) v -- There's no existing value we can take
306− Just fromKs ->
307− if k `Set.member` fromKs
308− then do
309− putRemainingKeys $ Set.delete k fromKs
310− return $ MapEdit_Move (Just undefined) k -- There's an existing value, and it's here, so no patch necessary
311− else do
312− Just (fromK, remainingKeys) <- return $ Set.minView fromKs -- There's an existing value, but it's not here; move it here
313− putRemainingKeys remainingKeys
314− return $ MapEdit_Move (Just undefined) fromK
315− Map.traverseWithKey f newByIndex
316− unusedOldKeys = fold unusedValuesByValue
317− pointlessMove k = \case
318− MapEdit_Move _ k' | k == k' -> True
319− _ -> False
320− keyWasMoved k = if k `Map.member` oldByIndex && not (k `Set.member` unusedOldKeys)
321− then Just undefined
322− else Nothing
323− patch = unsafePatchMapWithMove $ Map.filterWithKey (\k v -> not $ pointlessMove k v) $ Map.mergeWithKey (\k a _ -> Just $ mapEditSetMoved (keyWasMoved k) a) (Map.mapWithKey $ \k -> mapEditSetMoved $ keyWasMoved k) (Map.mapWithKey $ \k _ -> MapEdit_Delete $ keyWasMoved k) insertsAndMoves oldByIndex
324−
325−data DMapEdit (k :: a -> *) (v :: a -> *) :: a -> * where
326− -- | Insert the given value here
327− DMapEdit_Insert :: v a -> DMapEdit k v a
328− -- | Delete the existing value, if any, from here
329− DMapEdit_Delete :: DMapEdit k v a
330− -- | Move the value here from the given key
331− DMapEdit_Move :: k a -> DMapEdit k v a
332− deriving (Show, Read, Eq, Ord)
333−
334−instance {-# INCOHERENT #-} (GShow k, ShowTag k v) => ShowTag k (DMapEdit k v) where
335− showTagToShow k _ r = gshowToShow k $ showTagToShow k (Proxy :: Proxy v) r
336−
337−instance {-# INCOHERENT #-} GShow k => ShowTag k (ComposeMaybe k) where
338− showTagToShow k _ r = gshowToShow k (showTagToShow k (Proxy :: Proxy k) r)
339−
340−instance (GEq k, EqTag k v) => EqTag k (DMapEdit k v) where
341− eqTagToEq k p r = eqTagToEq k (Proxy :: Proxy v) (geqToEq k r)
342−
343−{-
344−dmapEditMoved :: DMapEdit k v a -> Bool
345−dmapEditMoved = isJust . dmapEditMovedTo
346−
347−dmapEditMovedTo :: DMapEdit k v a -> Maybe (k a)
348−dmapEditMovedTo = \case
349− DMapEdit_Insert moved _ -> moved
350− DMapEdit_Delete moved -> moved
351− DMapEdit_Move moved _ -> moved
352−-}
353−
354−-- | Like 'PatchMapWithMove', but for 'DMap'.
355−data PatchDMapWithMove k v = PatchDMapWithMove (DMap k (DMapEdit k v)) (DMap k (ComposeMaybe k))
356− deriving (Show)
357−
358−validPatchDMapWithMove :: forall k v. (GCompare k, EqTag k (ComposeMaybe k)) => PatchDMapWithMove k v -> Bool
359−validPatchDMapWithMove (PatchDMapWithMove dst src) = src == srcFromDst
360− where
361− srcFromDst :: DMap k (ComposeMaybe k)
362− srcFromDst = DMap.fromList $ flip fmap (DMap.toList dst) $ \(to :=> edit) -> case edit of
363− DMapEdit_Move from -> from :=> ComposeMaybe (Just to)
364− _ -> to :=> ComposeMaybe Nothing
365−
366−instance (Eq k, Eq v) => EqTag (Const2 k v) Identity where
367− eqTagToEq (Const2 _) _ = id
368−
369−instance (Eq k, Eq v) => EqTag (Const2 k v) (Const2 k v) where
370− eqTagToEq (Const2 _) _ = id
371−
372−moveDMapKey :: GCompare k => k a -> k a -> PatchDMapWithMove k v
373−moveDMapKey src dst = case src `geq` dst of
374− Nothing -> PatchDMapWithMove (DMap.fromList [dst :=> DMapEdit_Move src, src :=> DMapEdit_Delete]) (DMap.fromList [src :=> ComposeMaybe (Just dst), dst :=> ComposeMaybe Nothing])
375− Just _ -> mempty
376−
377−deleteDMapKey :: k a -> PatchDMapWithMove k v
378−deleteDMapKey k = PatchDMapWithMove (DMap.singleton k DMapEdit_Delete) $ DMap.singleton k $ ComposeMaybe Nothing
379−
380−{-
381−k1, k2 :: Const2 Int () ()
382−k1 = Const2 1
383−k2 = Const2 2
384−p1, p2 :: PatchDMapWithMove (Const2 Int ()) Identity
385−p1 = moveDMapKey k1 k2
386−p2 = moveDMapKey k2 k1
387−p12 = p1 <> p2
388−p21 = p2 <> p1
389−p12Slow = p1 `mappendPatchDMapWithMoveSlow` p2
390−p21Slow = p2 `mappendPatchDMapWithMoveSlow` p1
391−
392−testPatchDMapWithMove = do
393− print p1
394− print p2
395− print $ p12 == deleteDMapKey k1
396− print $ p21 == deleteDMapKey k2
397− print $ p12Slow == deleteDMapKey k1
398− print $ p21Slow == deleteDMapKey k2
399−
400−dst (PatchDMapWithMove x _) = x
401−src (PatchDMapWithMove _ x) = x
402−-}
403−
404−instance (EqTag k k, EqTag k v, EqTag k (ComposeMaybe k)) => Eq (PatchDMapWithMove k v) where
405− PatchDMapWithMove dstA srcA == PatchDMapWithMove dstB srcB = dstA == dstB && srcA == srcB
406−
407−data Pair1 f g a = Pair1 (f a) (g a)
408−
409−instance GCompare k => Semigroup (PatchDMapWithMove k v) where
410− (<>) = mappend
411−
412−instance GCompare k => Monoid (PatchDMapWithMove k v) where
413− mempty = PatchDMapWithMove mempty mempty
414− PatchDMapWithMove dstAfter srcAfter `mappend` PatchDMapWithMove dstBefore srcBefore = PatchDMapWithMove dst src
415− where
416− connections = DMap.toList $ DMap.intersectionWithKey (const Pair1) srcAfter dstBefore
417− f :: DSum k (Pair1 (ComposeMaybe k) (DMapEdit k v)) -> Maybe (DSum k (ComposeMaybe (DMapEdit k v)))
418− f (k :=> Pair1 (ComposeMaybe mToAfter) editBefore) = case mToAfter of
419− Nothing -> Nothing -- Since the result of this connection was simply discarded, we don't need to change dst
420− Just toAfter -> Just $ (toAfter :=>) $ ComposeMaybe $ case editBefore of
421− DMapEdit_Move fromBefore -> if isJust $ fromBefore `geq` toAfter
422− then Nothing -- The two moves have canceled out, so eliminate the destination entry
423− else Just editBefore
424− nonMove -> Just editBefore
425− dstPatch = PatchDMap $ DMap.fromList $ catMaybes $ f <$> connections
426− dst = applyAlways dstPatch $ DMap.union dstAfter dstBefore
427− g :: DSum k (Pair1 (ComposeMaybe k) (DMapEdit k v)) -> Maybe (DSum k (ComposeMaybe (ComposeMaybe k)))
428− g (k :=> Pair1 (ComposeMaybe mToAfter) editBefore) = case editBefore of
429− DMapEdit_Move fromBefore -> Just $ (fromBefore :=>) $ ComposeMaybe $ case mToAfter of
430− Nothing -> Just $ ComposeMaybe Nothing -- The item is destroyed in the second patch, so indicate that it is destroyed in the source map
431− Just toAfter -> if isJust $ toAfter `geq` fromBefore
432− then Nothing -- The two moves have canceled out, so eliminate the source entry
433− else Just $ ComposeMaybe mToAfter
434− _ -> Nothing --TODO: Is this right?
435− srcPatch = PatchDMap $ DMap.fromList $ catMaybes $ g <$> connections
436− src = applyAlways srcPatch $ DMap.union srcBefore srcAfter
437−
438−mappendPatchDMapWithMoveSlow :: forall k v. (ShowTag k v, GCompare k) => PatchDMapWithMove k v -> PatchDMapWithMove k v -> PatchDMapWithMove k v
439−PatchDMapWithMove dstAfter srcAfter `mappendPatchDMapWithMoveSlow` PatchDMapWithMove dstBefore srcBefore = PatchDMapWithMove dst src
440− where
441− getDstAction k m = fromMaybe (DMapEdit_Move k) $ DMap.lookup k m -- Any key that isn't present is treated as that key moving to itself
442− removeRedundantDst toKey (DMapEdit_Move fromKey) | isJust (toKey `geq` fromKey) = Nothing
443− removeRedundantDst _ a = Just a
444− f :: forall a. k a -> DMapEdit k v a -> Maybe (DMapEdit k v a)
445− f toKey _ = removeRedundantDst toKey $ case getDstAction toKey dstAfter of
446− DMapEdit_Move fromKey -> getDstAction fromKey dstBefore
447− nonMove -> nonMove
448− dst = DMap.mapMaybeWithKey f $ DMap.union dstAfter dstBefore
449− getSrcAction k m = fromMaybe (ComposeMaybe $ Just k) $ DMap.lookup k m
450− removeRedundantSrc fromKey (ComposeMaybe (Just toKey)) | isJust (fromKey `geq` toKey) = Nothing
451− removeRedundantSrc _ a = Just a
452− g :: forall a. k a -> ComposeMaybe k a -> Maybe (ComposeMaybe k a)
453− g fromKey _ = removeRedundantSrc fromKey $ case getSrcAction fromKey srcBefore of
454− ComposeMaybe Nothing -> ComposeMaybe Nothing
455− ComposeMaybe (Just toKeyBefore) -> getSrcAction toKeyBefore srcAfter
456− src = DMap.mapMaybeWithKey g $ DMap.union srcAfter srcBefore
457−
458−{-
459−unPatchDMapWithMove :: PatchDMapWithMove k v -> DMap k (DMapEdit k v)
460−unPatchDMapWithMove (PatchDMapWithMove p) = p
461−
462−-- | Warning: when using this function, you must ensure that the invariants of
463−-- 'PatchDMapWithMove' are preserved; they will not be checked.
464−unsafePatchDMapWithMove :: DMap k (DMapEdit k v) -> PatchDMapWithMove k v
465−unsafePatchDMapWithMove = PatchDMapWithMove
466−-}
467−
468−{-
469−instance GCompare k => Monoid (PatchDMapWithMove k v) where
470− mempty = PatchDMapWithMove mempty
471− PatchDMapWithMove a `mappend` PatchDMapWithMove b = PatchDMapWithMove $ DMap.unionWithKey f a b
472− where f :: forall a. k a -> DMapEdit k v a -> DMapEdit k v a -> DMapEdit k v a
473− f _ ea eb = case (ea, eb) of
474− (DMapEdit_Delete _, DMapEdit_Delete moved) -> DMapEdit_Delete $ do
475− dst <- moved
476− case DMap.lookup dst a of
477− Nothing -> return dst
478− Just dstChange -> dmapEditMovedTo dstChange
479− (DMapEdit_Move _ from, DMapEdit_Delete moved) -> DMapEdit_Move moved from
480− (DMapEdit_Insert _ v, DMapEdit_Delete moved) -> DMapEdit_Insert moved v
481− (DMapEdit_Delete _, DMapEdit_Insert moved v) -> DMapEdit_Delete moved
482− (DMapEdit_Move _ from, DMapEdit_Insert moved v) -> DMapEdit_Move moved from
483− (DMapEdit_Insert _ newV, DMapEdit_Insert moved oldV) -> DMapEdit_Insert moved newV
484− (DMapEdit_Delete _, DMapEdit_Move moved k) -> DMapEdit_Delete moved
485−-}
486−
487−{-
488−
489−mapPatchDMapWithMove :: forall k v v'. (forall a. v a -> v' a) -> PatchDMapWithMove k v -> PatchDMapWithMove k v'
490−mapPatchDMapWithMove f (PatchDMapWithMove p) = PatchDMapWithMove $ DMap.map g p
491− where g :: forall a. DMapEdit k v a -> DMapEdit k v' a
492− g = \case
493− DMapEdit_Insert moved v -> DMapEdit_Insert moved $ f v
494− DMapEdit_Delete moved -> DMapEdit_Delete moved
495− DMapEdit_Move moved k -> DMapEdit_Move moved k
496−
497−traversePatchDMapWithMove :: forall m k v v'. Applicative m => (forall a. v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
498−traversePatchDMapWithMove f = traversePatchDMapWithMoveWithKey $ const f
499−
500−traversePatchDMapWithMoveWithKey :: forall m k v v'. Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
501−traversePatchDMapWithMoveWithKey f (PatchDMapWithMove p) = PatchDMapWithMove <$> DMap.traverseWithKey g p
502− where g :: forall a. k a -> DMapEdit k v a -> m (DMapEdit k v' a)
503− g k = \case
504− DMapEdit_Insert moved v -> DMapEdit_Insert moved <$> f k v
505− DMapEdit_Delete moved -> pure $ DMapEdit_Delete moved
506− DMapEdit_Move moved fromKey -> pure $ DMapEdit_Move moved fromKey
507−
508−weakenPatchDMapWithMoveWith :: forall k v v'. (forall a. v a -> v') -> PatchDMapWithMove k v -> PatchMapWithMove (Some k) v'
509−weakenPatchDMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ weakenDMapWith g p
510− where g :: forall a. DMapEdit k v a -> MapEdit (Some k) v'
511− g = \case
512− DMapEdit_Insert moved v -> MapEdit_Insert (Some.This <$> moved) $ f v
513− DMapEdit_Delete moved -> MapEdit_Delete $ Some.This <$> moved
514− DMapEdit_Move moved k -> MapEdit_Move (Some.This <$> moved) $ Some.This k
515−
516−patchDMapWithMoveToPatchMapWithMoveWith :: forall k f v v'. (f v -> v') -> PatchDMapWithMove (Const2 k v) f -> PatchMapWithMove k v'
517−patchDMapWithMoveToPatchMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ dmapToMapWith g p
518− where g :: DMapEdit (Const2 k v) f v -> MapEdit k v'
519− g = \case
520− DMapEdit_Insert moved v -> MapEdit_Insert (unConst2 <$> moved) $ f v
521− DMapEdit_Delete moved -> MapEdit_Delete $ unConst2 <$> moved
522− DMapEdit_Move moved (Const2 k) -> MapEdit_Move (unConst2 <$> moved) k
523−
524−const2PatchDMapWithMoveWith :: forall k v f a. (v -> f a) -> PatchMapWithMove k v -> PatchDMapWithMove (Const2 k a) f
525−const2PatchDMapWithMoveWith f (PatchMapWithMove p) = PatchDMapWithMove $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
526− where g :: (k, MapEdit k v) -> DSum (Const2 k a) (DMapEdit (Const2 k a) f)
527− g (k, e) = Const2 k :=> case e of
528− MapEdit_Insert moved v -> DMapEdit_Insert (Const2 <$> moved) $ f v
529− MapEdit_Delete moved -> DMapEdit_Delete $ Const2 <$> moved
530− MapEdit_Move moved fromKey -> DMapEdit_Move (Const2 <$> moved) $ Const2 fromKey
531−
532−instance GCompare k => Patch (PatchDMapWithMove k v) where
533− type PatchTarget (PatchDMapWithMove k v) = DMap k v
534− apply (PatchDMapWithMove p) old = Just $! insertions `DMap.union` (old `DMap.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?
535− where insertions = DMap.mapMaybeWithKey insertFunc p
536− insertFunc :: forall a. k a -> DMapEdit k v a -> Maybe (v a)
537− insertFunc _ = \case
538− DMapEdit_Insert _ v -> Just v
539− DMapEdit_Move _ k -> DMap.lookup k old
540− DMapEdit_Delete _ -> Nothing
541− deletions = DMap.mapMaybeWithKey deleteFunc p
542− deleteFunc :: forall a. k a -> DMapEdit k v a -> Maybe (Constant () a)
543− deleteFunc _ = \case
544− DMapEdit_Delete _ -> Just $ Constant ()
545− _ -> Nothing
546−-}
src/Reflex/Patch/Class.hsnew file+20
@@ -0,0 +1,20 @@
1+{-# LANGUAGE TypeFamilies #-}
2+module Reflex.Patch.Class where
3+
4+import Control.Monad.Identity
5+import Data.Maybe
6+
7+-- | A 'Patch' type represents a kind of change made to a datastructure.
8+class Patch p where
9+ type PatchTarget p :: *
10+ -- | Apply the patch @p a@ to the value @a@. If no change is needed, return
11+ -- 'Nothing'.
12+ apply :: p -> PatchTarget p -> Maybe (PatchTarget p)
13+
14+applyAlways :: Patch p => p -> PatchTarget p -> PatchTarget p
15+applyAlways p t = fromMaybe t $ apply p t
16+
17+instance Patch (Identity a) where
18+ type PatchTarget (Identity a) = a
19+ apply (Identity a) _ = Just a
20+
src/Reflex/Patch/DMap.hsnew file+57
@@ -0,0 +1,57 @@
1+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE PolyKinds #-}
4+{-# LANGUAGE Rank2Types #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE StandaloneDeriving #-}
7+{-# LANGUAGE TypeFamilies #-}
8+module Reflex.Patch.DMap where
9+
10+import Reflex.Patch.Class
11+import Reflex.Patch.Map
12+
13+import Data.Dependent.Map (DMap, DSum (..), GCompare (..))
14+import qualified Data.Dependent.Map as DMap
15+import Data.Functor.Constant
16+import Data.Functor.Misc
17+import qualified Data.Map as Map
18+import Data.Semigroup (Semigroup (..))
19+import Data.Some (Some)
20+
21+-- | A set of changes to a 'DMap'. Any element may be inserted/updated or
22+-- deleted.
23+newtype PatchDMap k v = PatchDMap (DMap k (ComposeMaybe v))
24+
25+deriving instance GCompare k => Semigroup (PatchDMap k v)
26+
27+deriving instance GCompare k => Monoid (PatchDMap k v)
28+
29+instance GCompare k => Patch (PatchDMap k v) where
30+ type PatchTarget (PatchDMap k v) = DMap k v
31+ apply (PatchDMap diff) old = Just $! insertions `DMap.union` (old `DMap.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?
32+ where insertions = DMap.mapMaybeWithKey (const $ getComposeMaybe) diff
33+ deletions = DMap.mapMaybeWithKey (const $ nothingToJust . getComposeMaybe) diff
34+ nothingToJust = \case
35+ Nothing -> Just $ Constant ()
36+ Just _ -> Nothing
37+
38+mapPatchDMap :: (forall a. v a -> v' a) -> PatchDMap k v -> PatchDMap k v'
39+mapPatchDMap f (PatchDMap p) = PatchDMap $ DMap.map (ComposeMaybe . fmap f . getComposeMaybe) p
40+
41+traversePatchDMap :: Applicative m => (forall a. v a -> m (v' a)) -> PatchDMap k v -> m (PatchDMap k v')
42+traversePatchDMap f = traversePatchDMapWithKey $ const f
43+
44+traversePatchDMapWithKey :: Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMap k v -> m (PatchDMap k v')
45+traversePatchDMapWithKey f (PatchDMap p) = PatchDMap <$> DMap.traverseWithKey (\k (ComposeMaybe v) -> ComposeMaybe <$> traverse (f k) v) p
46+
47+weakenPatchDMapWith :: (forall a. v a -> v') -> PatchDMap k v -> PatchMap (Some k) v'
48+weakenPatchDMapWith f (PatchDMap p) = PatchMap $ weakenDMapWith (fmap f . getComposeMaybe) p
49+
50+patchDMapToPatchMapWith :: (f v -> v') -> PatchDMap (Const2 k v) f -> PatchMap k v'
51+patchDMapToPatchMapWith f (PatchDMap p) = PatchMap $ dmapToMapWith (fmap f . getComposeMaybe) p
52+
53+const2PatchDMapWith :: forall k v f a. (v -> f a) -> PatchMap k v -> PatchDMap (Const2 k a) f
54+const2PatchDMapWith f (PatchMap p) = PatchDMap $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
55+ where g :: (k, Maybe v) -> DSum (Const2 k a) (ComposeMaybe f)
56+ g (k, e) = Const2 k :=> ComposeMaybe (f <$> e)
57+
src/Reflex/Patch/DMapWithMove.hsnew file+275
@@ -0,0 +1,275 @@
1+-- | This module defines the 'Patch' class, which is used by Reflex to manage
2+-- changes to 'Reflex.Class.Incremental' values.
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE LambdaCase #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE PatternGuards #-}
9+{-# LANGUAGE PolyKinds #-}
10+{-# LANGUAGE Rank2Types #-}
11+{-# LANGUAGE ScopedTypeVariables #-}
12+{-# LANGUAGE TypeFamilies #-}
13+{-# LANGUAGE UndecidableInstances #-}
14+module Reflex.Patch.DMapWithMove where
15+
16+import Reflex.Patch.Class
17+import Reflex.Patch.MapWithMove (PatchMapWithMove (..))
18+import qualified Reflex.Patch.MapWithMove as MapWithMove
19+
20+import Data.Dependent.Map (DMap, DSum (..), GCompare (..))
21+import qualified Data.Dependent.Map as DMap
22+import Data.Dependent.Sum (ShowTag (..), EqTag (..))
23+import Data.Functor.Constant
24+import Data.Functor.Misc
25+import Data.GADT.Compare (GEq (..))
26+import Data.GADT.Show (GShow (..))
27+import qualified Data.Map as Map
28+import Data.Maybe
29+import Data.Semigroup (Semigroup (..), (<>))
30+import Data.Some (Some)
31+import qualified Data.Some as Some
32+import Data.These
33+import Data.Typeable (Proxy (..))
34+
35+-- | Like 'PatchMapWithMove', but for 'DMap'.
36+data PatchDMapWithMove k v = PatchDMapWithMove (DMap k (NodeInfo k v))
37+ deriving (Show)
38+
39+data NodeInfo k v a = NodeInfo
40+ { _nodeInfo_from :: !(From k v a)
41+ , _nodeInfo_to :: !(To k a)
42+ }
43+ deriving (Show)
44+
45+instance {-# INCOHERENT #-} (GShow k, ShowTag k v) => ShowTag k (NodeInfo k v) where
46+ showTagToShow k _ r = gshowToShow k $ showTagToShow k (Proxy :: Proxy v) r
47+
48+instance {-# INCOHERENT #-} (GEq k, EqTag k v) => EqTag k (From k v) where
49+ eqTagToEq k _ r = eqTagToEq k (Proxy :: Proxy v) (geqToEq k r)
50+
51+data From (k :: a -> *) (v :: a -> *) :: a -> * where
52+ From_Insert :: v a -> From k v a
53+ From_Delete :: From k v a
54+ From_Move :: !(k a) -> From k v a
55+ deriving (Show, Read, Eq, Ord)
56+
57+type To = ComposeMaybe
58+
59+validPatchDMapWithMove :: forall k v. (GCompare k, EqTag k (ComposeMaybe k)) => PatchDMapWithMove k v -> Bool
60+validPatchDMapWithMove (PatchDMapWithMove m) = src == srcFromDst
61+ where
62+ src = DMap.map _nodeInfo_to m
63+ dst = DMap.map _nodeInfo_from m
64+ srcFromDst :: DMap k (ComposeMaybe k)
65+ srcFromDst = DMap.fromList $ flip fmap (DMap.toList dst) $ \(to :=> edit) -> case edit of
66+ From_Move from -> from :=> ComposeMaybe (Just to)
67+ _ -> to :=> ComposeMaybe Nothing
68+
69+instance EqTag k (NodeInfo k v) => Eq (PatchDMapWithMove k v) where
70+ PatchDMapWithMove a == PatchDMapWithMove b = a == b
71+
72+data Pair1 f g a = Pair1 (f a) (g a)
73+
74+instance GCompare k => Semigroup (PatchDMapWithMove k v) where
75+ (<>) = mappend
76+
77+data Fixup k v a
78+ = Fixup_Delete
79+ | Fixup_Update (These (From k v a) (To k a))
80+
81+instance GCompare k => Monoid (PatchDMapWithMove k v) where
82+ mempty = PatchDMapWithMove mempty
83+ PatchDMapWithMove ma `mappend` PatchDMapWithMove mb = PatchDMapWithMove m
84+ where
85+ connections = DMap.toList $ DMap.intersectionWithKey (\_ a b -> Pair1 (_nodeInfo_to a) (_nodeInfo_from b)) ma mb
86+ h :: DSum k (Pair1 (ComposeMaybe k) (From k v)) -> [DSum k (Fixup k v)]
87+ h (_ :=> Pair1 (ComposeMaybe mToAfter) editBefore) = case (mToAfter, editBefore) of
88+ (Just toAfter, From_Move fromBefore)
89+ | isJust $ fromBefore `geq` toAfter
90+ -> [toAfter :=> Fixup_Delete]
91+ | otherwise
92+ -> [ toAfter :=> Fixup_Update (This editBefore)
93+ , fromBefore :=> Fixup_Update (That (ComposeMaybe mToAfter))
94+ ]
95+ (Nothing, From_Move fromBefore) -> [fromBefore :=> Fixup_Update (That (ComposeMaybe mToAfter))] -- The item is destroyed in the second patch, so indicate that it is destroyed in the source map
96+ (Just toAfter, _) -> [toAfter :=> Fixup_Update (This editBefore)]
97+ (Nothing, _) -> []
98+ mergeFixups _ Fixup_Delete Fixup_Delete = Fixup_Delete
99+ mergeFixups _ (Fixup_Update a) (Fixup_Update b)
100+ | This x <- a, That y <- b
101+ = Fixup_Update $ These x y
102+ | That y <- a, This x <- b
103+ = Fixup_Update $ These x y
104+ mergeFixups _ _ _ = error "PatchDMapWithMove: incompatible fixups"
105+ fixups = DMap.fromListWithKey mergeFixups $ concatMap h connections
106+ combineNodeInfos _ nia nib = NodeInfo
107+ { _nodeInfo_from = _nodeInfo_from nia
108+ , _nodeInfo_to = _nodeInfo_to nib
109+ }
110+ applyFixup _ ni = \case
111+ Fixup_Delete -> Nothing
112+ Fixup_Update u -> Just $ NodeInfo
113+ { _nodeInfo_from = fromMaybe (_nodeInfo_from ni) $ getHere u
114+ , _nodeInfo_to = fromMaybe (_nodeInfo_to ni) $ getThere u
115+ }
116+ m = DMap.differenceWithKey applyFixup (DMap.unionWithKey combineNodeInfos ma mb) fixups
117+
118+getHere :: These a b -> Maybe a
119+getHere = \case
120+ This a -> Just a
121+ These a _ -> Just a
122+ That _ -> Nothing
123+
124+getThere :: These a b -> Maybe b
125+getThere = \case
126+ This _ -> Nothing
127+ These _ b -> Just b
128+ That b -> Just b
129+
130+{-
131+mappendPatchDMapWithMoveSlow :: forall k v. (ShowTag k v, GCompare k) => PatchDMapWithMove k v -> PatchDMapWithMove k v -> PatchDMapWithMove k v
132+PatchDMapWithMove dstAfter srcAfter `mappendPatchDMapWithMoveSlow` PatchDMapWithMove dstBefore srcBefore = PatchDMapWithMove dst src
133+ where
134+ getDstAction k m = fromMaybe (From_Move k) $ DMap.lookup k m -- Any key that isn't present is treated as that key moving to itself
135+ removeRedundantDst toKey (From_Move fromKey) | isJust (toKey `geq` fromKey) = Nothing
136+ removeRedundantDst _ a = Just a
137+ f :: forall a. k a -> From k v a -> Maybe (From k v a)
138+ f toKey _ = removeRedundantDst toKey $ case getDstAction toKey dstAfter of
139+ From_Move fromKey -> getDstAction fromKey dstBefore
140+ nonMove -> nonMove
141+ dst = DMap.mapMaybeWithKey f $ DMap.union dstAfter dstBefore
142+ getSrcAction k m = fromMaybe (ComposeMaybe $ Just k) $ DMap.lookup k m
143+ removeRedundantSrc fromKey (ComposeMaybe (Just toKey)) | isJust (fromKey `geq` toKey) = Nothing
144+ removeRedundantSrc _ a = Just a
145+ g :: forall a. k a -> ComposeMaybe k a -> Maybe (ComposeMaybe k a)
146+ g fromKey _ = removeRedundantSrc fromKey $ case getSrcAction fromKey srcBefore of
147+ ComposeMaybe Nothing -> ComposeMaybe Nothing
148+ ComposeMaybe (Just toKeyBefore) -> getSrcAction toKeyBefore srcAfter
149+ src = DMap.mapMaybeWithKey g $ DMap.union srcAfter srcBefore
150+-}
151+
152+moveDMapKey :: GCompare k => k a -> k a -> PatchDMapWithMove k v
153+moveDMapKey src dst = case src `geq` dst of
154+ Nothing -> PatchDMapWithMove $ DMap.fromList
155+ [ dst :=> NodeInfo (From_Move src) (ComposeMaybe Nothing)
156+ , src :=> NodeInfo From_Delete (ComposeMaybe $ Just dst)
157+ ]
158+ Just _ -> mempty
159+
160+deleteDMapKey :: k a -> PatchDMapWithMove k v
161+deleteDMapKey k = PatchDMapWithMove $ DMap.singleton k $ NodeInfo From_Delete $ ComposeMaybe Nothing
162+
163+{-
164+k1, k2 :: Const2 Int () ()
165+k1 = Const2 1
166+k2 = Const2 2
167+p1, p2 :: PatchDMapWithMove (Const2 Int ()) Identity
168+p1 = moveDMapKey k1 k2
169+p2 = moveDMapKey k2 k1
170+p12 = p1 <> p2
171+p21 = p2 <> p1
172+p12Slow = p1 `mappendPatchDMapWithMoveSlow` p2
173+p21Slow = p2 `mappendPatchDMapWithMoveSlow` p1
174+
175+testPatchDMapWithMove = do
176+ print p1
177+ print p2
178+ print $ p12 == deleteDMapKey k1
179+ print $ p21 == deleteDMapKey k2
180+ print $ p12Slow == deleteDMapKey k1
181+ print $ p21Slow == deleteDMapKey k2
182+
183+dst (PatchDMapWithMove x _) = x
184+src (PatchDMapWithMove _ x) = x
185+-}
186+
187+unPatchDMapWithMove :: PatchDMapWithMove k v -> DMap k (NodeInfo k v)
188+unPatchDMapWithMove (PatchDMapWithMove p) = p
189+
190+-- | Warning: when using this function, you must ensure that the invariants of
191+-- 'PatchDMapWithMove' are preserved; they will not be checked.
192+unsafePatchDMapWithMove :: DMap k (NodeInfo k v) -> PatchDMapWithMove k v
193+unsafePatchDMapWithMove = PatchDMapWithMove
194+
195+mapPatchDMapWithMove :: forall k v v'. (forall a. v a -> v' a) -> PatchDMapWithMove k v -> PatchDMapWithMove k v'
196+mapPatchDMapWithMove f (PatchDMapWithMove p) = PatchDMapWithMove $
197+ DMap.map (\ni -> ni { _nodeInfo_from = g $ _nodeInfo_from ni }) p
198+ where g :: forall a. From k v a -> From k v' a
199+ g = \case
200+ From_Insert v -> From_Insert $ f v
201+ From_Delete -> From_Delete
202+ From_Move k -> From_Move k
203+
204+traversePatchDMapWithMove :: forall m k v v'. Applicative m => (forall a. v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
205+traversePatchDMapWithMove f = traversePatchDMapWithMoveWithKey $ const f
206+
207+traversePatchDMapWithMoveWithKey :: forall m k v v'. Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMapWithMove k v -> m (PatchDMapWithMove k v')
208+traversePatchDMapWithMoveWithKey f (PatchDMapWithMove p) = PatchDMapWithMove <$> DMap.traverseWithKey (nodeInfoMapFromM . g) p
209+ where g :: forall a. k a -> From k v a -> m (From k v' a)
210+ g k = \case
211+ From_Insert v -> From_Insert <$> f k v
212+ From_Delete -> pure From_Delete
213+ From_Move fromKey -> pure $ From_Move fromKey
214+
215+nodeInfoMapFrom :: (From k v a -> From k v' a) -> NodeInfo k v a -> NodeInfo k v' a
216+nodeInfoMapFrom f ni = ni { _nodeInfo_from = f $ _nodeInfo_from ni }
217+
218+nodeInfoMapFromM :: Functor f => (From k v a -> f (From k v' a)) -> NodeInfo k v a -> f (NodeInfo k v' a)
219+nodeInfoMapFromM f ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _nodeInfo_from ni
220+
221+weakenPatchDMapWithMoveWith :: forall k v v'. (forall a. v a -> v') -> PatchDMapWithMove k v -> PatchMapWithMove (Some k) v'
222+weakenPatchDMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ weakenDMapWith g p
223+ where g :: forall a. NodeInfo k v a -> MapWithMove.NodeInfo (Some k) v'
224+ g ni = MapWithMove.NodeInfo
225+ { MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
226+ From_Insert v -> MapWithMove.From_Insert $ f v
227+ From_Delete -> MapWithMove.From_Delete
228+ From_Move k -> MapWithMove.From_Move $ Some.This k
229+ , MapWithMove._nodeInfo_to = Some.This <$> getComposeMaybe (_nodeInfo_to ni)
230+ }
231+
232+patchDMapWithMoveToPatchMapWithMoveWith :: forall k f v v'. (f v -> v') -> PatchDMapWithMove (Const2 k v) f -> PatchMapWithMove k v'
233+patchDMapWithMoveToPatchMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ dmapToMapWith g p
234+ where g :: NodeInfo (Const2 k v) f v -> MapWithMove.NodeInfo k v'
235+ g ni = MapWithMove.NodeInfo
236+ { MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
237+ From_Insert v -> MapWithMove.From_Insert $ f v
238+ From_Delete -> MapWithMove.From_Delete
239+ From_Move (Const2 k) -> MapWithMove.From_Move k
240+ , MapWithMove._nodeInfo_to = unConst2 <$> getComposeMaybe (_nodeInfo_to ni)
241+ }
242+
243+const2PatchDMapWithMoveWith :: forall k v f a. (v -> f a) -> PatchMapWithMove k v -> PatchDMapWithMove (Const2 k a) f
244+const2PatchDMapWithMoveWith f (PatchMapWithMove p) = PatchDMapWithMove $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
245+ where g :: (k, MapWithMove.NodeInfo k v) -> DSum (Const2 k a) (NodeInfo (Const2 k a) f)
246+ g (k, ni) = Const2 k :=> NodeInfo
247+ { _nodeInfo_from = case MapWithMove._nodeInfo_from ni of
248+ MapWithMove.From_Insert v -> From_Insert $ f v
249+ MapWithMove.From_Delete -> From_Delete
250+ MapWithMove.From_Move fromKey -> From_Move $ Const2 fromKey
251+ , _nodeInfo_to = ComposeMaybe $ Const2 <$> MapWithMove._nodeInfo_to ni
252+ }
253+
254+instance GCompare k => Patch (PatchDMapWithMove k v) where
255+ type PatchTarget (PatchDMapWithMove k v) = DMap k v
256+ apply (PatchDMapWithMove p) old = Just $! insertions `DMap.union` (old `DMap.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?
257+ where insertions = DMap.mapMaybeWithKey insertFunc p
258+ insertFunc :: forall a. k a -> NodeInfo k v a -> Maybe (v a)
259+ insertFunc _ ni = case _nodeInfo_from ni of
260+ From_Insert v -> Just v
261+ From_Move k -> DMap.lookup k old
262+ From_Delete -> Nothing
263+ deletions = DMap.mapMaybeWithKey deleteFunc p
264+ deleteFunc :: forall a. k a -> NodeInfo k v a -> Maybe (Constant () a)
265+ deleteFunc _ ni = case _nodeInfo_from ni of
266+ From_Delete -> Just $ Constant ()
267+ _ -> Nothing
268+
269+-- | Get the values that will be deleted if the given patch is applied to the
270+-- given 'DMap'
271+getDeletions :: GCompare k => PatchDMapWithMove k v -> DMap k v' -> DMap k v'
272+getDeletions (PatchDMapWithMove p) m = DMap.mapMaybeWithKey (\_ -> getComposeMaybe) $ DMap.intersectionWithKey f p m
273+ where f _ ni v = ComposeMaybe $ case getComposeMaybe $ _nodeInfo_to ni of
274+ Just _ -> Nothing
275+ Nothing -> Just v
src/Reflex/Patch/Map.hsnew file+48
@@ -0,0 +1,48 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE TypeFamilies #-}
4+module Reflex.Patch.Map where
5+
6+import Reflex.Patch.Class
7+
8+import Data.Semigroup
9+import Data.Map (Map)
10+import qualified Data.Map as Map
11+import Data.Maybe
12+
13+-- | A set of changes to a 'Map'. Any element may be inserted/updated or
14+-- deleted.
15+newtype PatchMap k v = PatchMap { unPatchMap :: Map k (Maybe v) }
16+
17+instance Ord k => Patch (PatchMap k v) where
18+ type PatchTarget (PatchMap k v) = Map k v
19+ apply (PatchMap 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?
20+ where insertions = Map.mapMaybeWithKey (const id) p
21+ deletions = Map.mapMaybeWithKey (const nothingToJust) p
22+ nothingToJust = \case
23+ Nothing -> Just ()
24+ Just _ -> Nothing
25+
26+instance Ord k => Semigroup (PatchMap k v) where
27+ PatchMap a <> PatchMap b = PatchMap $ a `mappend` b --TODO: Add a semigroup instance for Map
28+ -- PatchMap is idempotent, so stimes n is id for every n
29+#if MIN_VERSION_semigroups(0,17,0)
30+ stimes = stimesIdempotentMonoid
31+#else
32+ times1p n x = case compare n 0 of
33+ LT -> error "stimesIdempotentMonoid: negative multiplier"
34+ EQ -> mempty
35+ GT -> x
36+#endif
37+
38+instance Ord k => Monoid (PatchMap k v) where
39+ mempty = PatchMap mempty
40+ mappend = (<>)
41+
42+instance Functor (PatchMap k) where
43+ fmap f = PatchMap . fmap (fmap f) . unPatchMap
44+
45+-- | Returns all the new elements that will be added to the 'Map'
46+patchMapNewElements :: PatchMap k v -> [v]
47+patchMapNewElements (PatchMap p) = catMaybes $ Map.elems p
48+
src/Reflex/Patch/MapWithMove.hsnew file+156
@@ -0,0 +1,156 @@
1+{-# LANGUAGE DeriveFoldable #-}
2+{-# LANGUAGE DeriveFunctor #-}
3+{-# LANGUAGE DeriveTraversable #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE LambdaCase #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE TypeFamilies #-}
8+module Reflex.Patch.MapWithMove where
9+
10+import Reflex.Patch.Class
11+
12+import Control.Arrow
13+import Control.Monad.State
14+import Data.Foldable
15+import Data.Function
16+import Data.List
17+import Data.Map (Map)
18+import qualified Data.Map as Map
19+import Data.Maybe
20+import qualified Data.Set as Set
21+import Data.Tuple
22+
23+-- | Patch a DMap with additions, deletions, and moves.
24+-- Invariants:
25+-- * The same source key may not be moved to two destination keys.
26+-- * Any key that is the source of a Move must also be edited
27+-- (otherwise, the Move would duplicate it)
28+data PatchMapWithMove k v = PatchMapWithMove (Map k (NodeInfo k v)) deriving (Show, Eq, Ord, Functor, Foldable, Traversable)
29+
30+data NodeInfo k v = NodeInfo
31+ { _nodeInfo_from :: !(From k v)
32+ , _nodeInfo_to :: !(To k)
33+ }
34+ deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
35+
36+unPatchMapWithMove :: PatchMapWithMove k v -> Map k (NodeInfo k v)
37+unPatchMapWithMove (PatchMapWithMove p) = p
38+
39+-- | Warning: when using this function, you must ensure that the invariants of
40+-- 'PatchMapWithMove' are preserved; they will not be checked.
41+unsafePatchMapWithMove :: Map k (NodeInfo k v) -> PatchMapWithMove k v
42+unsafePatchMapWithMove = PatchMapWithMove
43+
44+data From k v
45+ = From_Insert v -- ^ Insert the given value here
46+ | From_Delete -- ^ Delete the existing value, if any, from here
47+ | From_Move !k -- ^ Move the value here from the given key
48+ deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable)
49+
50+type To = Maybe
51+
52+instance Ord k => Patch (PatchMapWithMove k v) where
53+ type PatchTarget (PatchMapWithMove k v) = Map k v
54+ 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?
55+ where insertions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
56+ From_Insert v -> Just v
57+ From_Move k -> Map.lookup k old
58+ From_Delete -> Nothing
59+ deletions = flip Map.mapMaybeWithKey p $ \_ ni -> case _nodeInfo_from ni of
60+ From_Delete -> Just ()
61+ _ -> Nothing
62+
63+-- | Returns all the new elements that will be added to the 'Map'
64+patchMapWithMoveNewElements :: PatchMapWithMove k v -> [v]
65+patchMapWithMoveNewElements (PatchMapWithMove p) = catMaybes $ f <$> Map.elems p
66+ where f ni = case _nodeInfo_from ni of
67+ From_Insert v -> Just v
68+ From_Move _ -> Nothing
69+ From_Delete -> Nothing
70+
71+validPatchMapWithMove :: Ord k => PatchMapWithMove k v -> Bool
72+validPatchMapWithMove (PatchMapWithMove p) =
73+ let f ni = case _nodeInfo_from ni of
74+ From_Move k -> Just k
75+ _ -> Nothing
76+ moveSources = catMaybes $ f <$> Map.elems p
77+ in and
78+ [ -- All Moves must have unique source keys - a single source key can't be moved to two destinations
79+ all (==1) $ Map.fromListWith (+) $ fmap (\k -> (k, 1 :: Integer )) moveSources
80+ , -- All Move source keys must also be patched - since the value's being moved elsewhere, the original must either be Deleted or replaced (with Insert or Move)
81+ all (isJust . flip Map.lookup p) moveSources
82+ ]
83+
84+knownValidPatchMapWithMove :: [PatchMapWithMove Int Int]
85+knownValidPatchMapWithMove =
86+ [ PatchMapWithMove mempty
87+ , PatchMapWithMove $ Map.fromList
88+ [ (1, NodeInfo (From_Move 2) $ Just 1)
89+ , (2, NodeInfo (From_Move 1) $ Just 2)
90+ ]
91+ ]
92+
93+knownInvalidPatchMapWithMove :: [PatchMapWithMove Int Int]
94+knownInvalidPatchMapWithMove =
95+ [ -- Invalid because we move 2 to 1, but don't specify what to do with 2
96+ PatchMapWithMove $ Map.singleton 1 $ NodeInfo (From_Move 2) Nothing
97+ , -- Invalid because we have multiple moves from 3
98+ PatchMapWithMove $ Map.fromList
99+ [ (1, NodeInfo (From_Move 3) Nothing)
100+ , (2, NodeInfo (From_Move 3) Nothing)
101+ , (3, NodeInfo From_Delete (Just 1))
102+ ]
103+ ]
104+
105+patchThatSortsMapWith :: Ord k => (v -> v -> Ordering) -> Map k v -> PatchMapWithMove k v
106+patchThatSortsMapWith cmp m = PatchMapWithMove $ Map.fromList $ catMaybes $ zipWith g unsorted sorted
107+ where unsorted = Map.toList m
108+ sorted = sortBy (cmp `on` snd) unsorted
109+ f (to, _) (from, _) = if to == from then Nothing else
110+ Just (from, to)
111+ reverseMapping = Map.fromList $ catMaybes $ zipWith f unsorted sorted
112+ g (to, _) (from, _) = if to == from then Nothing else
113+ let Just movingTo = Map.lookup from reverseMapping
114+ in Just (to, NodeInfo (From_Move from) $ Just movingTo)
115+
116+patchThatChangesAndSortsMapWith :: forall k v. (Ord k, Ord v) => (v -> v -> Ordering) -> Map k v -> Map k v -> PatchMapWithMove k v
117+patchThatChangesAndSortsMapWith cmp oldByIndex newByIndexUnsorted = patch
118+ where oldByValue = Map.fromListWith Set.union $ swap . first Set.singleton <$> Map.toList oldByIndex
119+ newList = Map.toList newByIndexUnsorted
120+ newByIndex = Map.fromList $ zip (fst <$> newList) $ sortBy cmp $ snd <$> newList
121+ insertsAndMoves :: Map k (NodeInfo k v)
122+ (insertsAndMoves, unusedValuesByValue) = flip runState oldByValue $ do
123+ let f k v = do
124+ remainingValues <- get
125+ let putRemainingKeys remainingKeys = put $ if Set.null remainingKeys
126+ then Map.delete v remainingValues
127+ else Map.insert v remainingKeys remainingValues
128+ case Map.lookup v remainingValues of
129+ Nothing -> return $ NodeInfo (From_Insert v) $ Just undefined -- There's no existing value we can take
130+ Just fromKs ->
131+ if k `Set.member` fromKs
132+ then do
133+ putRemainingKeys $ Set.delete k fromKs
134+ return $ NodeInfo (From_Move k) $ Just undefined -- There's an existing value, and it's here, so no patch necessary
135+ else do
136+ Just (fromK, remainingKeys) <- return $ Set.minView fromKs -- There's an existing value, but it's not here; move it here
137+ putRemainingKeys remainingKeys
138+ return $ NodeInfo (From_Move fromK) $ Just undefined
139+ Map.traverseWithKey f newByIndex
140+ unusedOldKeys = fold unusedValuesByValue
141+ pointlessMove k = \case
142+ From_Move k' | k == k' -> True
143+ _ -> False
144+ keyWasMoved k = if k `Map.member` oldByIndex && not (k `Set.member` unusedOldKeys)
145+ then Just undefined
146+ else Nothing
147+ 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
148+
149+nodeInfoMapFrom :: (From k v -> From k v) -> NodeInfo k v -> NodeInfo k v
150+nodeInfoMapFrom f ni = ni { _nodeInfo_from = f $ _nodeInfo_from ni }
151+
152+nodeInfoMapMFrom :: Functor f => (From k v -> f (From k v)) -> NodeInfo k v -> f (NodeInfo k v)
153+nodeInfoMapMFrom f ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _nodeInfo_from ni
154+
155+nodeInfoSetTo :: To k -> NodeInfo k v -> NodeInfo k v
156+nodeInfoSetTo to ni = ni { _nodeInfo_to = to }