on develop help sign in
Merge remote-tracking branch 'origin/develop' into patch-map-inner-patch
commit0dfcb56f2b6bd6230957c71f2c6625046732dfd6 authorJohn Ericson <git@JohnEricson.me> dateSat 11 Jan 2020, 00:54 (7 years ago) parent1e4d080eb6b6e5
10 files changed+158−23
.travis.yml+3−5 ChangeLog.md+6 README.md+47 cabal.haskell-ci+7 patch.cabal+14−4 release.nix+1 src/Data/Patch.hs+56 src/Data/Patch/Class.hs+6 src/Data/Patch/DMapWithMove.hs+10−9 src/Data/Patch/MapWithMove.hs+8−5
.travis.yml+3−5
@@ -139,12 +139,12 @@ script:
139139 - cd ${DISTDIR} || false
140140 - find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
141141 - find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \;
142− - PKGDIR_reflex="$(find . -maxdepth 1 -type d -regex '.*/reflex-[0-9.]*')"
142+ - PKGDIR_patch="$(find . -maxdepth 1 -type d -regex '.*/patch-[0-9.]*')"
143143 # Generate cabal.project
144144 - rm -rf cabal.project cabal.project.local cabal.project.freeze
145145 - touch cabal.project
146146 - |
147− echo "packages: ${PKGDIR_reflex}" >> cabal.project
147+ echo "packages: ${PKGDIR_patch}" >> cabal.project
148148 - |
149149 - cat cabal.project || true
150150 - cat cabal.project.local || true
@@ -157,13 +157,11 @@ script:
157157 # Testing...
158158 - if ! $GHCJS ; then ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output ; fi
159159 # cabal check...
160− - (cd ${PKGDIR_reflex} && ${CABAL} -vnormal check)
160+ - (cd ${PKGDIR_patch} && ${CABAL} -vnormal check)
161161 # haddock...
162162 - if ! $GHCJS ; then ${CABAL} v2-haddock $WITHCOMPILER ${TEST} ${BENCH} all | color_cabal_output ; fi
163163 # Constraint sets
164164 - rm -rf cabal.project.local
165− # Constraint set no-th
166− - ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='reflex -use-template-haskell' all | color_cabal_output
167165 # Constraint set old-these
168166 - if $GHCJS || ! $GHCJS && [ $HCNUMVER -lt 80800 ] ; then ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='these <1' all | color_cabal_output ; fi
169167 # Constraint set old-witherable
ChangeLog.md+6
@@ -4,6 +4,12 @@
44
55* `PatchMapWithMove` supports moves with a patch.
66
7+## 0.0.1.0
8+
9+* Support older GHCs with `split-these` flag.
10+
11+* Additional instances for the `Group` class for basic types.
12+
713## 0.0.0.1
814
915* Remove unneeded dependencies
README.md+47
@@ -1,3 +1,50 @@
11## Patch
22
33Infrastructure for writing patches which act on other types.
4+
5+A `Patch` type represents a kind of change made to a datastructure.
6+
7+```haskell
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+
15+### Patching Maps
16+For example, `Data.Patch.Map` defines the `PatchMap` type which can be used to patch `Map`s. A `PatchMap` represents updates to a `Map` that can insert, remove, or replace items in the `Map`. In this example, the `Map` is the `PatchTarget` and the `PatchMap` is the `Patch`. Keep in mind that there are many other possible `Patch`es that can be applied to a `Map` (i.e., `Map` can be the `PatchTarget` for many different `Patch` instances).
17+
18+`PatchMap` is defined as:
19+
20+```haskell
21+newtype PatchMap k v = PatchMap { unPatchMap :: Map k (Maybe v) }
22+```
23+
24+The `Maybe` around the value is used to represent insertion/updates or deletion of the element at a given key.
25+
26+Its `Patch` instance begins with:
27+
28+```haskell
29+instance Ord k => Patch (PatchMap k v) where
30+ type PatchTarget (PatchMap k v) = Map k v
31+ ...
32+```
33+
34+When a `PatchMap` is applied to its `PatchTarget`, the following changes can occur:
35+
36+- If the key is present in the `Patch` and the `PatchTarget`...
37+
38+ - And the `Patch` value at that key is `Nothing`: delete that key from the `PatchTarget`.
39+
40+ - And the `Patch` value at that key is `Just x`: update the value at that key in the `PatchTarget` to `x`.
41+
42+- If the key is present in the `Patch` and not present in the `PatchTarget`...
43+
44+ - And the `Patch` value at that key is `Nothing`: do nothing because we're trying to delete a key that doesn't exist in the target in the first place.
45+
46+ - And the `Patch` value at that key is `Just x`: insert the key and the value `x` into the `PatchTarget`
47+
48+- If the key is *not* present in the `Patch` but present in the `PatchTarget`: do nothing.
49+
50+There are, of course, more complicated ways of patching maps involving, for example, moving values from one key to another. You can find the code for that in `Data.Patch.PatchMapWithMove`. Note that the `PatchTarget` type associated with the `PatchMapWithMove` patch instance is still `Map k v`!
cabal.haskell-ci+7
@@ -5,3 +5,10 @@ installed: -all
55
66-- https://github.com/haskell/cabal/issues/6106
77install-dependencies: False
8+
9+constraint-set old-these
10+ ghc: <8.8
11+ constraints: these <1
12+
13+constraint-set old-witherable
14+ constraints: witherable <0.3.2
patch.cabal+14−4
@@ -1,5 +1,5 @@
11Name: patch
2−Version: 0.0.0.1
2+Version: 0.0.1.0
33Synopsis: Infrastructure for writing patches which act on other types.
44Description:
55 In this library, a patch is something which can be applied, analogous to a
@@ -23,6 +23,11 @@ tested-with:
2323 GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || ==8.8.1
2424 GHCJS ==8.4
2525
26+flag split-these
27+ description: Use split these/semialign packages
28+ manual: False
29+ default: True
30+
2631library
2732 hs-source-dirs: src
2833 build-depends: base >= 4.9 && < 4.14
@@ -30,10 +35,7 @@ library
3035 , containers >= 0.6 && < 0.7
3136 , dependent-map >= 0.3 && < 0.4
3237 , dependent-sum >= 0.6 && < 0.7
33− , monoidal-containers >= 0.6 && < 0.7
34− , semialign >=1 && <1.2
3538 , semigroupoids >= 4.0 && < 6
36− , these >= 1 && <1.1
3739 , transformers >= 0.5.6.0 && < 0.6
3840 , witherable >= 0.3 && < 0.3.2
3941
@@ -48,6 +50,14 @@ library
4850
4951 ghc-options: -Wall -fwarn-redundant-constraints -fwarn-tabs
5052
53+ if flag(split-these)
54+ build-depends: these >= 1 && <1.1
55+ , semialign >=1 && <1.2
56+ , monoidal-containers >= 0.6 && < 0.7
57+ else
58+ build-depends: these >= 0.4 && <0.9
59+ , monoidal-containers == 0.4.0.0
60+
5161test-suite hlint
5262 type: exitcode-stdio-1.0
5363 main-is: hlint.hs
release.nix+1
@@ -22,6 +22,7 @@ let
2222 "release.nix"
2323 ".git"
2424 "dist"
25+ "dist-newstyle"
2526 "cabal.haskell-ci"
2627 "cabal.project"
2728 ".travis.yml"
src/Data/Patch.hs+56
@@ -1,4 +1,8 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE StandaloneDeriving #-}
14{-# LANGUAGE TypeFamilies #-}
5+{-# LANGUAGE TypeOperators #-}
26-- |
37-- Module:
48-- Data.Patch
@@ -10,7 +14,9 @@ module Data.Patch
1014 ) where
1115
1216import Data.Map.Monoidal (MonoidalMap)
17+#if !MIN_VERSION_base(4,9,0)
1318import Data.Semigroup (Semigroup (..), (<>))
19+#endif
1420
1521import Data.Patch.Class as X
1622import Data.Patch.DMap as X hiding (getDeletions)
@@ -23,6 +29,11 @@ import Data.Patch.Map as X
2329import Data.Patch.MapWithMove as X (PatchMapWithMove, patchMapWithMoveNewElements,
2430 patchMapWithMoveNewElementsMap, unPatchMapWithMove,
2531 unsafePatchMapWithMove)
32+import GHC.Generics
33+import Data.Functor.Identity
34+import Data.Functor.Const
35+import Data.Proxy
36+import Control.Applicative
2637
2738-- | A 'Group' is a 'Monoid' where every element has an inverse.
2839class (Semigroup q, Monoid q) => Group q where
@@ -44,3 +55,48 @@ instance (Ord k, Group q) => Group (MonoidalMap k q) where
4455 negateG = fmap negateG
4556
4657instance (Ord k, Additive q) => Additive (MonoidalMap k q)
58+
59+-- | Trivial group.
60+instance Group () where
61+ negateG _ = ()
62+ _ ~~ _ = ()
63+instance Additive ()
64+
65+-- | Product group. A Pair of groups gives rise to a group
66+instance (Group a, Group b) => Group (a, b) where
67+ negateG (a, b) = (negateG a, negateG b)
68+ (a, b) ~~ (c, d) = (a ~~ c, b ~~ d)
69+instance (Additive a, Additive b) => Additive (a, b)
70+
71+-- See https://gitlab.haskell.org/ghc/ghc/issues/11135#note_111802 for the reason Compose is not also provided.
72+-- Base does not define Monoid (Compose f g a) so this is the best we can
73+-- really do for functor composition.
74+instance Group (f (g a)) => Group ((f :.: g) a) where
75+ negateG (Comp1 xs) = Comp1 (negateG xs)
76+ Comp1 xs ~~ Comp1 ys = Comp1 (xs ~~ ys)
77+instance Additive (f (g a)) => Additive ((f :.: g) a)
78+
79+-- | Product of groups, Functor style.
80+instance (Group (f a), Group (g a)) => Group ((f :*: g) a) where
81+ negateG (a :*: b) = negateG a :*: negateG b
82+ (a :*: b) ~~ (c :*: d) = (a ~~ c) :*: (b ~~ d)
83+instance (Additive (f a), Additive (g a)) => Additive ((f :*: g) a)
84+
85+-- | Trivial group, Functor style
86+instance Group (Proxy x) where
87+ negateG _ = Proxy
88+ _ ~~ _ = Proxy
89+instance Additive (Proxy x)
90+
91+-- | Const lifts groups into a functor.
92+deriving instance Group a => Group (Const a x)
93+instance Additive a => Additive (Const a x)
94+-- | Ideitnty lifts groups pointwise (at only one point)
95+deriving instance Group a => Group (Identity a)
96+instance Additive a => Additive (Identity a)
97+
98+-- | Functions lift groups pointwise.
99+instance Group b => Group (a -> b) where
100+ negateG f = negateG . f
101+ (~~) = liftA2 (~~)
102+instance Additive b => Additive (a -> b)
src/Data/Patch/Class.hs+6
@@ -5,6 +5,7 @@ module Data.Patch.Class where
55import Data.Functor.Identity
66import Data.Maybe
77import Data.Semigroup (Semigroup(..))
8+import Data.Proxy
89
910-- | A 'Patch' type represents a kind of change made to a datastructure.
1011--
@@ -25,6 +26,11 @@ instance Patch (Identity a) where
2526 type PatchTarget (Identity a) = a
2627 apply (Identity a) _ = Just a
2728
29+-- | 'Identity' can be used as a 'Patch' that always fully replaces the value
30+instance Patch (Proxy a) where
31+ type PatchTarget (Proxy a) = a
32+ apply ~Proxy _ = Nothing
33+
2834-- | Like '(.)', but composes functions that return patches rather than
2935-- functions that return new values. The Semigroup instance for patches must
3036-- apply patches right-to-left, like '(.)'.
src/Data/Patch/DMapWithMove.hs+10−9
@@ -31,6 +31,7 @@ import qualified Data.Map as Map
3131import Data.Maybe
3232import Data.Semigroup (Semigroup (..), (<>))
3333import Data.Some (Some(Some))
34+import Data.Proxy
3435import Data.These
3536
3637-- | Like 'PatchMapWithMove', but for 'DMap'. Each key carries a 'NodeInfo' which describes how it will be changed by the patch and connects move sources and
@@ -303,41 +304,41 @@ nodeInfoMapFromM f ni = fmap (\result -> ni { _nodeInfo_from = result }) $ f $ _
303304
304305-- |Weaken a 'PatchDMapWithMove' to a 'PatchMapWithMove' by weakening the keys from @k a@ to @'Some' k@ and applying a given weakening function @v a -> v'@ to
305306-- values.
306−weakenPatchDMapWithMoveWith :: forall k v v'. (forall a. v a -> v') -> PatchDMapWithMove k v -> PatchMapWithMove (Some k) v'
307+weakenPatchDMapWithMoveWith :: forall k v v'. (forall a. v a -> v') -> PatchDMapWithMove k v -> PatchMapWithMove (Some k) (Proxy v')
307308weakenPatchDMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ weakenDMapWith g p
308− where g :: forall a. NodeInfo k v a -> MapWithMove.NodeInfo (Some k) v'
309+ where g :: forall a. NodeInfo k v a -> MapWithMove.NodeInfo (Some k) (Proxy v')
309310 g ni = MapWithMove.NodeInfo
310311 { MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
311312 From_Insert v -> MapWithMove.From_Insert $ f v
312313 From_Delete -> MapWithMove.From_Delete
313− From_Move k -> MapWithMove.From_Move $ Some k
314+ From_Move k -> MapWithMove.From_Move (Some k) Proxy
314315 , MapWithMove._nodeInfo_to = Some <$> getComposeMaybe (_nodeInfo_to ni)
315316 }
316317
317318-- |"Weaken" a @'PatchDMapWithMove' (Const2 k a) v@ to a @'PatchMapWithMove' k v'@. Weaken is in scare quotes because the 'Const2' has already disabled any
318319-- dependency in the typing and all points are already @a@, hence the function to map each value to @v'@ is not higher rank.
319−patchDMapWithMoveToPatchMapWithMoveWith :: forall k v v' a. (v a -> v') -> PatchDMapWithMove (Const2 k a) v -> PatchMapWithMove k v'
320+patchDMapWithMoveToPatchMapWithMoveWith :: forall k v v' a. (v a -> v') -> PatchDMapWithMove (Const2 k a) v -> PatchMapWithMove k (Proxy v')
320321patchDMapWithMoveToPatchMapWithMoveWith f (PatchDMapWithMove p) = PatchMapWithMove $ dmapToMapWith g p
321− where g :: NodeInfo (Const2 k a) v a -> MapWithMove.NodeInfo k v'
322+ where g :: NodeInfo (Const2 k a) v a -> MapWithMove.NodeInfo k (Proxy v')
322323 g ni = MapWithMove.NodeInfo
323324 { MapWithMove._nodeInfo_from = case _nodeInfo_from ni of
324325 From_Insert v -> MapWithMove.From_Insert $ f v
325326 From_Delete -> MapWithMove.From_Delete
326− From_Move (Const2 k) -> MapWithMove.From_Move k
327+ From_Move (Const2 k) -> MapWithMove.From_Move k Proxy
327328 , MapWithMove._nodeInfo_to = unConst2 <$> getComposeMaybe (_nodeInfo_to ni)
328329 }
329330
330331-- |"Strengthen" a @'PatchMapWithMove' k v@ into a @'PatchDMapWithMove ('Const2' k a)@; that is, turn a non-dependently-typed patch into a dependently typed
331332-- one but which always has a constant key type represented by 'Const2'. Apply the given function to each @v@ to produce a @v' a@.
332333-- Completemented by 'patchDMapWithMoveToPatchMapWithMoveWith'
333−const2PatchDMapWithMoveWith :: forall k v v' a. (v -> v' a) -> PatchMapWithMove k v -> PatchDMapWithMove (Const2 k a) v'
334+const2PatchDMapWithMoveWith :: forall k v v' a. (v -> v' a) -> PatchMapWithMove k (Proxy v) -> PatchDMapWithMove (Const2 k a) v'
334335const2PatchDMapWithMoveWith f (PatchMapWithMove p) = PatchDMapWithMove $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
335− where g :: (k, MapWithMove.NodeInfo k v) -> DSum (Const2 k a) (NodeInfo (Const2 k a) v')
336+ where g :: (k, MapWithMove.NodeInfo k (Proxy v)) -> DSum (Const2 k a) (NodeInfo (Const2 k a) v')
336337 g (k, ni) = Const2 k :=> NodeInfo
337338 { _nodeInfo_from = case MapWithMove._nodeInfo_from ni of
338339 MapWithMove.From_Insert v -> From_Insert $ f v
339340 MapWithMove.From_Delete -> From_Delete
340− MapWithMove.From_Move fromKey -> From_Move $ Const2 fromKey
341+ MapWithMove.From_Move fromKey Proxy -> From_Move $ Const2 fromKey
341342 , _nodeInfo_to = ComposeMaybe $ Const2 <$> MapWithMove._nodeInfo_to ni
342343 }
343344
src/Data/Patch/MapWithMove.hs+8−5
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE FlexibleContexts #-}
23{-# LANGUAGE LambdaCase #-}
34{-# LANGUAGE PatternGuards #-}
@@ -7,25 +8,27 @@
78{-# LANGUAGE UndecidableInstances #-}
89-- | 'Patch'es on 'Map' that can insert, delete, and move values from one key to
910-- another
10−module Reflex.Patch.MapWithMove where
11+module Data.Patch.MapWithMove where
1112
12−import Reflex.Patch.Class
13+import Data.Patch.Class
1314
1415import Control.Arrow
15−import Control.Monad.State
16+import Control.Monad.Trans.State
1617import Data.Foldable
1718import Data.Function
1819import Data.List
1920import Data.Map (Map)
2021import qualified Data.Map as Map
2122import Data.Maybe
23+#if !MIN_VERSION_base(4,9,0)
2224import Data.Semigroup
25+#endif
2326import qualified Data.Set as Set
2427import Data.These
2528import Data.Tuple
2629
27−-- | Patch a DMap with additions, deletions, and moves. Invariant: If key @k1@
28−-- is coming from @From_Move k@, then key @k2@ should be going to @Just k1@,
30+-- | Patch a Map with additions, deletions, and moves. Invariant: If key @k1@
31+-- is coming from @From_Move k2@, then key @k2@ should be going to @Just k1@,
2932-- and vice versa. There should never be any unpaired From/To keys.
3033newtype PatchMapWithMove k p = PatchMapWithMove (Map k (NodeInfo k p))
3134deriving instance (Show k, Show p, Show (PatchTarget p)) => Show (PatchMapWithMove k p)