|
| 1 | +{-# LANGUAGE BangPatterns #-} |
| 2 | +{-# LANGUAGE ConstraintKinds #-} |
| 3 | +{-# LANGUAGE DeriveAnyClass #-} |
| 4 | +{-# LANGUAGE DeriveGeneric #-} |
| 5 | +{-# LANGUAGE DerivingStrategies #-} |
| 6 | +{-# LANGUAGE FlexibleContexts #-} |
| 7 | +{-# LANGUAGE FlexibleInstances #-} |
| 8 | +{-# LANGUAGE GeneralizedNewtypeDeriving #-} |
| 9 | +{-# LANGUAGE ScopedTypeVariables #-} |
| 10 | +{-# LANGUAGE StandaloneDeriving #-} |
| 11 | +{-# LANGUAGE TypeApplications #-} |
| 12 | +{-# LANGUAGE TypeFamilies #-} |
| 13 | + |
| 14 | +-- | Introduce the Incremental Lambda Calculus embodied in the ILC class. |
| 15 | +-- Instances for two patterns of use involving Maps. |
| 16 | +module Data.Incremental where |
| 17 | + |
| 18 | +import Control.DeepSeq (NFData (..)) |
| 19 | +import Data.Kind |
| 20 | +import Data.Map.Internal (Map (..)) |
| 21 | +import Data.Map.Strict |
| 22 | +import qualified Data.Map.Strict as Map |
| 23 | +import GHC.Generics (Generic (..)) |
| 24 | + |
| 25 | +-- =================================================== |
| 26 | +-- Incremental lambda calculus |
| 27 | + |
| 28 | +class ILC t where |
| 29 | + data Diff t :: Type |
| 30 | + applyDiff :: t -> Diff t -> t |
| 31 | + extend :: Diff t -> Diff t -> Diff t |
| 32 | + zero :: Diff t |
| 33 | + |
| 34 | +infixr 0 $$ |
| 35 | +($$) :: ILC t => t -> Diff t -> t |
| 36 | +x $$ y = applyDiff x y |
| 37 | + |
| 38 | +-- | Every (Diff t) is a Semigroup |
| 39 | +instance ILC t => Semigroup (Diff t) where |
| 40 | + x <> y = extend x y |
| 41 | + |
| 42 | +-- | Every (Diff t) is a Monoid |
| 43 | +instance ILC t => Monoid (Diff t) where |
| 44 | + mempty = zero |
| 45 | + |
| 46 | +-- ============================================================== |
| 47 | +-- Delta types. |
| 48 | +-- We are going to give the type (Map dom rng) an ILC instance. |
| 49 | +-- It turns out there are two reasonable choices for Map. The two |
| 50 | +-- reasonable choices differ on what properties the range of the Map |
| 51 | +-- has. If the range of the Map is a monoid, there are 3 ways the map |
| 52 | +-- might change. |
| 53 | +-- 1) entry is deleted, |
| 54 | +-- 2) an entry is changed or created, so there is a new range value |
| 55 | +-- 3) the range of an entry is combined (using monoid (actually semigroup) <>) with another value. |
| 56 | +-- |
| 57 | +-- If the range is not a Monoid there are only two ways the map might change |
| 58 | +-- 1) entry is deleted, |
| 59 | +-- 2) an entry is changed or created, so there is a new range value |
| 60 | +-- |
| 61 | +-- To do this we introduce two datatypes MonoidRngD and BinaryRngD. They |
| 62 | +-- will become part of the definition for the Diff(Map dom rng). It also |
| 63 | +-- turns out thet Both of them are Semigroups (but not Monoids as neither |
| 64 | +-- has a notion of No-Change. This is deliberate, but might be reconsidered |
| 65 | +-- at some point) |
| 66 | + |
| 67 | +-- | The range is deleted, overwritten, or combined using a Monoid |
| 68 | +data MonoidRngD v = Del | Write !v | Comb !v |
| 69 | + deriving (Show, Eq, Generic, NFData) |
| 70 | + |
| 71 | +instance (Semigroup t) => Semigroup (MonoidRngD t) where |
| 72 | + Del <> Del = Del |
| 73 | + Del <> Write _ = Del |
| 74 | + Del <> Comb _ = Del |
| 75 | + Comb x <> Del = Write x |
| 76 | + Comb x <> Write y = Write (x <> y) |
| 77 | + Comb x <> Comb y = Comb (x <> y) |
| 78 | + Write x <> Del = Write x |
| 79 | + Write x <> Comb _ = Write x |
| 80 | + Write x <> Write _ = Write x |
| 81 | + |
| 82 | +-- | The range is deleted or changed |
| 83 | +data BinaryRngD v = Omit | Edit !v |
| 84 | + deriving (Eq, Generic, NFData) |
| 85 | + |
| 86 | +-- The show instance is manual because it supports cutting and pasting |
| 87 | +-- error messages, to get values for exploring failures. With out the |
| 88 | +-- parantheses they often won't read properly. |
| 89 | +instance Show v => Show (BinaryRngD v) where |
| 90 | + show Omit = "Omit" |
| 91 | + show (Edit d) = "Edit(" ++ show d ++ ")" |
| 92 | + |
| 93 | +instance Semigroup (BinaryRngD t) where |
| 94 | + Omit <> Omit = Omit |
| 95 | + Omit <> Edit _ = Omit |
| 96 | + Edit x <> Omit = Edit x |
| 97 | + Edit x <> Edit _ = Edit x |
| 98 | + |
| 99 | +-- ============================================================ |
| 100 | +-- Since there are two reasonable ILC instances for the Map |
| 101 | +-- type we wrap the map in a newtype for the first instance. |
| 102 | +-- This is the special case of a Map where the range is a |
| 103 | +-- Monoid. We provide tools to enforce the invariant, that in a |
| 104 | +-- MonoidMap, we never store 'mempty' of the Monoid. |
| 105 | + |
| 106 | +newtype MonoidMap k v = MM (Map k v) |
| 107 | + deriving newtype (Show, Eq, NFData) |
| 108 | + |
| 109 | +unMM :: MonoidMap k v -> Map k v |
| 110 | +unMM (MM x) = x |
| 111 | + |
| 112 | +monoidInsertWith :: (Monoid v, Eq v, Ord k) => k -> v -> MonoidMap k v -> MonoidMap k v |
| 113 | +monoidInsertWith k !v1 (MM m) = MM (alter ok k m) |
| 114 | + where |
| 115 | + ok Nothing = if v1 == mempty then Nothing else Just v1 |
| 116 | + ok (Just v2) = if total == mempty then Nothing else Just total |
| 117 | + where |
| 118 | + total = v1 <> v2 |
| 119 | +{-# INLINEABLE monoidInsertWith #-} |
| 120 | + |
| 121 | +monoidInsert :: (Monoid v, Eq v, Ord k) => k -> v -> MonoidMap k v -> MonoidMap k v |
| 122 | +monoidInsert k !v1 (MM m) = if v1 == mempty then MM (delete k m) else MM (insert k v1 m) |
| 123 | +{-# INLINEABLE monoidInsert #-} |
| 124 | + |
| 125 | +-- ========================================= |
| 126 | +-- ILC instances |
| 127 | + |
| 128 | +-- | Monoidal maps have special properties, so they get their |
| 129 | +-- own instance (wrapped in the newtype). |
| 130 | +instance (Ord k, Eq v, ILC v, Monoid v) => ILC (MonoidMap k v) where |
| 131 | + newtype Diff (MonoidMap k v) = Dm (Map k (MonoidRngD (Diff v))) |
| 132 | + applyDiff mm (Dm md) = Map.foldlWithKey' accum mm md |
| 133 | + where |
| 134 | + accum :: MonoidMap k v -> k -> MonoidRngD (Diff v) -> MonoidMap k v |
| 135 | + accum (MM ans) cred Del = MM (Map.delete cred ans) |
| 136 | + accum ans cred (Comb dv) = |
| 137 | + monoidInsertWith cred (applyDiff mempty dv) ans |
| 138 | + accum ans cred (Write dv) = monoidInsert cred (applyDiff mempty dv) ans |
| 139 | + {-# INLINEABLE applyDiff #-} |
| 140 | + zero = Dm Map.empty |
| 141 | + extend (Dm x) (Dm y) = Dm (Map.unionWith (<>) x y) |
| 142 | + |
| 143 | +instance (Show (Diff v), Show k) => Show (Diff (MonoidMap k v)) where |
| 144 | + show (Dm x) = show (Map.toList x) |
| 145 | + |
| 146 | +deriving newtype instance (NFData k, NFData (Diff v)) => NFData (Diff (MonoidMap k v)) |
| 147 | + |
| 148 | +-- | Normal map can only be deleted or updated so they use BinaryRngD |
| 149 | +instance Ord k => ILC (Map k v) where |
| 150 | + newtype Diff (Map k v) = Dn (Map k (BinaryRngD v)) |
| 151 | + applyDiff m (Dn md) = Map.foldlWithKey' accum m md |
| 152 | + where |
| 153 | + accum ans k Omit = Map.delete k ans |
| 154 | + accum ans k (Edit drep) = Map.insert k drep ans |
| 155 | + {-# INLINEABLE applyDiff #-} |
| 156 | + zero = Dn Map.empty |
| 157 | + extend (Dn x) (Dn y) = Dn (Map.unionWith (<>) x y) |
| 158 | + |
| 159 | +instance (Show k, Show v) => Show (Diff (Map k v)) where |
| 160 | + show (Dn x) = show (Map.toList x) |
| 161 | + |
| 162 | +deriving newtype instance (NFData k, NFData v) => NFData (Diff (Map k v)) |
| 163 | + |
| 164 | +-- ================================================================= |
| 165 | +-- helper functions for making binary derivatives |
| 166 | + |
| 167 | +-- | insert a change (MonoidRngD c) into a Map. |
| 168 | +-- Note that if we wrap the (result :: Map k (MonoidRngD c)) with the constructor 'Dn' |
| 169 | +-- Dn :: Map k (BinaryRngD v) -> Diff (Map k v) |
| 170 | +-- then we get Diff(Map k v) |
| 171 | +insertC :: |
| 172 | + (Ord k, Monoid c) => |
| 173 | + k -> |
| 174 | + MonoidRngD c -> |
| 175 | + Map k (MonoidRngD c) -> |
| 176 | + Map k (MonoidRngD c) |
| 177 | +insertC d m x = insertWith (<>) d m x |
| 178 | + |
| 179 | +-- | Split two maps, x and y, into three parts |
| 180 | +-- 1) the key appears only in x |
| 181 | +-- 2) the key appears in both x and y |
| 182 | +-- 3) the key appears only in y |
| 183 | +-- Given three 'C'ontinuation style functions, reduce |
| 184 | +-- the three parts to a single value. |
| 185 | +inter3C :: |
| 186 | + Ord k => |
| 187 | + a -> |
| 188 | + Map k u -> |
| 189 | + Map k v -> |
| 190 | + (a -> k -> u -> a) -> |
| 191 | + (a -> k -> (u, v) -> a) -> |
| 192 | + (a -> k -> v -> a) -> |
| 193 | + a |
| 194 | +inter3C ans0 m0 n0 c1 c2 c3 = go ans0 m0 n0 |
| 195 | + where |
| 196 | + go ans Tip Tip = ans |
| 197 | + go !ans m Tip = Map.foldlWithKey' c1 ans m |
| 198 | + go !ans Tip n = Map.foldlWithKey' c3 ans n |
| 199 | + go !ans (Bin _ kx x l r) n = case Map.splitLookup kx n of |
| 200 | + (ln, Nothing, rn) -> go (go (c1 ans kx x) l ln) r rn |
| 201 | + (ln, Just y, rn) -> go (go (c2 ans kx (x, y)) l ln) r rn |
0 commit comments