on develop help sign in
complete doc comments for Data.Map.Misc, Reflex.FastWeak, Reflex.Patch.DMap, Reflex.Patch.DMapWithMove, Reflex.Patch.IntMap, and Reflex.Patch.Map
commite79511edaa3419116d41ec61b5ee1c4bb8b9c512 authorRoss MacLeod <ross@confer.health> dateSat 9 Jun 2018, 23:25 (8 years ago) parentc2d5c14
6 files changed+103−27
src/Data/Map/Misc.hs+24−9 src/Reflex/FastWeak.hs+46−5 src/Reflex/Patch/DMap.hs+16−8 src/Reflex/Patch/DMapWithMove.hs+3−2 src/Reflex/Patch/IntMap.hs+13−1 src/Reflex/Patch/Map.hs+1−2
src/Data/Map/Misc.hs+24−9
@@ -1,4 +1,5 @@
11{-# LANGUAGE LambdaCase #-}
2+-- |Additional functions for manipulating 'Map's.
23module Data.Map.Misc
34 (
45 -- * Working with Maps
@@ -10,7 +11,6 @@ module Data.Map.Misc
1011 ) where
1112
1213import Data.Align
13−import Data.Either
1414import Data.Map (Map)
1515import qualified Data.Map as Map
1616import Data.Maybe
@@ -18,12 +18,21 @@ import Data.Set (Set)
1818import qualified Data.Set as Set
1919import Data.These
2020
21+-- |Produce a @'Map' k (Maybe v)@ by comparing two @'Map' k v@s, @old@ and @new@ respectively. @Just@ represents an association present in @new@ and @Nothing@
22+-- represents an association only present in @old@ but no longer present in @new@.
23+--
24+-- Similar to 'diffMap' but doesn't require 'Eq' on the values, thus can't tell if a value has changed or not.
2125diffMapNoEq :: (Ord k) => Map k v -> Map k v -> Map k (Maybe v)
2226diffMapNoEq olds news = flip Map.mapMaybe (align olds news) $ \case
2327 This _ -> Just Nothing
2428 These _ new -> Just $ Just new
2529 That new -> Just $ Just new
2630
31+-- |Produce a @'Map' k (Maybe v)@ by comparing two @'Map' k v@s, @old@ and @new respectively. @Just@ represents an association present in @new@ and either not
32+-- present in @old@ or where the value has changed. @Nothing@ represents an association only present in @old@ but no longer present in @new@.
33+--
34+-- See also 'diffMapNoEq' for a similar but weaker version which does not require 'Eq' on the values but thus can't indicated a value not changing between
35+-- @old@ and @new@ with @Nothing@.
2736diffMap :: (Ord k, Eq v) => Map k v -> Map k v -> Map k (Maybe v)
2837diffMap olds news = flip Map.mapMaybe (align olds news) $ \case
2938 This _ -> Just Nothing
@@ -32,6 +41,9 @@ diffMap olds news = flip Map.mapMaybe (align olds news) $ \case
3241 | otherwise -> Just $ Just new
3342 That new -> Just $ Just new
3443
44+-- |Given a @'Map' k (Maybe v)@ representing keys to insert/update (@Just@) or delete (@Nothing@), produce a new map from the given input @'Map' k v@.
45+--
46+-- See also 'Reflex.Patch.Map' and 'Reflex.Patch.MapWithMove'.
3547applyMap :: Ord k => Map k (Maybe v) -> Map k v -> Map k v
3648applyMap patch old = insertions `Map.union` (old `Map.difference` deletions)
3749 where (deletions, insertions) = mapPartitionEithers $ maybeToEither <$> patch
@@ -39,16 +51,19 @@ applyMap patch old = insertions `Map.union` (old `Map.difference` deletions)
3951 Nothing -> Left ()
4052 Just r -> Right r
4153
54+-- |Split a @'Map' k (Either a b)@ into @Map k a@ and @Map k b@, equivalent to @'Map.mapEither' id@
4255mapPartitionEithers :: Map k (Either a b) -> (Map k a, Map k b)
43−mapPartitionEithers m = (fromLeft <$> ls, fromRight <$> rs)
44− where (ls, rs) = Map.partition isLeft m
45− fromLeft (Left l) = l
46− fromLeft _ = error "mapPartitionEithers: fromLeft received a Right value; this should be impossible"
47− fromRight (Right r) = r
48− fromRight _ = error "mapPartitionEithers: fromRight received a Left value; this should be impossible"
56+mapPartitionEithers = Map.mapEither id
4957
50−-- | Apply a map patch to a set
51−-- > applyMapKeysSet patch (Map.keysSet m) == Map.keysSet (applyMap patch m)
58+-- |Given a @'Map' k (Maybe v)@ representing keys to insert/update (@Just@) or delete (@Nothing@), produce a new @'Set' k@ from the given input set.
59+--
60+-- Equivalent to:
61+--
62+-- @
63+-- applyMapKeysSet patch ('Map.keysSet' m) == 'Map.keysSet' ('applyMap' patch m)
64+-- @
65+--
66+-- but avoids the intervening @Map@ and needs no values.
5267applyMapKeysSet :: Ord k => Map k (Maybe v) -> Set k -> Set k
5368applyMapKeysSet patch old = Map.keysSet insertions `Set.union` (old `Set.difference` Map.keysSet deletions)
5469 where (insertions, deletions) = Map.partition isJust patch
src/Reflex/FastWeak.hs+46−5
@@ -5,6 +5,15 @@
55{-# LANGUAGE ForeignFunctionInterface #-}
66{-# LANGUAGE JavaScriptFFI #-}
77#endif
8+
9+-- |Contains 'FastWeak', a weak pointer to some value, and 'FastWeakTicket' which ensures the value referred to by a 'FastWeak' stays live while the ticket is
10+-- held (live).
11+--
12+-- On GHC or GHCJS when not built with the @fast-weak@ cabal flag, 'FastWeak' is a wrapper around the simple version of 'System.Mem.Weak.Weak' where the key
13+-- and value are the same.
14+--
15+-- On GHCJS when built with the @fast-weak@ cabal flag, 'FastWeak' is implemented directly in JS using @h$FastWeak@ and @h$FastWeakTicket@ which are a
16+-- nonstandard part of the GHCJS RTS. __FIXME__
817module Reflex.FastWeak
918 ( FastWeakTicket
1019 , FastWeak
@@ -35,29 +44,52 @@ import System.Mem.Weak
3544
3645
3746#ifdef GHCJS_FAST_WEAK
47+-- |A 'FastWeak' which has been promoted to a strong reference. 'getFastWeakTicketValue' can be used to get the referred to value without fear of @Nothing,
48+-- and 'getFastWeakTicketWeak' can be used to get the weak version.
49+--
50+-- Implemented by way of special support in the GHCJS RTS, @h$FastWeakTicket@.
3851newtype FastWeakTicket a = FastWeakTicket JSVal
3952
53+-- |A reference to some value which can be garbage collected if there are only weak references to the value left.
54+--
55+-- 'getFastWeakValue' can be used to try and obtain a strong reference to the value.
56+--
57+-- The value in a @FastWeak@ can also be kept alive by obtaining a 'FastWeakTicket' using 'getFastWeakTicket' if the value hasn't been collected yet.
58+--
59+-- Implemented by way of special support in the GHCJS RTS, @h$FastWeak@.
4060newtype FastWeak a = FastWeak JSVal
4161
4262-- Just designed to mirror JSVal, so that we can unsafeCoerce between the two
4363data Val a = Val { unVal :: a }
4464
45−-- | Coerce a JSVal that represents the heap object of a value of type 'a' into a value of type 'a'
65+-- | Coerce a JSVal that represents the heap object of a value of type @a@ into a value of type @a@
4666unsafeFromRawJSVal :: JSVal -> a
4767unsafeFromRawJSVal v = unVal (unsafeCoerce v)
4868
69+-- | Coerce a heap object of type @a@ into a 'JSVal' which represents that object.
4970unsafeToRawJSVal :: a -> JSVal
5071unsafeToRawJSVal v = unsafeCoerce (Val v)
5172#else
73+-- |A 'FastWeak' which has been promoted to a strong reference. 'getFastWeakTicketValue' can be used to get the referred to value without fear of @Nothing,
74+-- and 'getFastWeakTicketWeak' can be used to get the weak version.
5275data FastWeakTicket a = FastWeakTicket
5376 { _fastWeakTicket_val :: !a
5477 , _fastWeakTicket_weak :: {-# UNPACK #-} !(Weak a)
5578 }
5679
80+-- |A reference to some value which can be garbage collected if there are only weak references to the value left.
81+--
82+-- 'getFastWeakValue' can be used to try and obtain a strong reference to the value.
83+--
84+-- The value in a @FastWeak@ can also be kept alive by obtaining a 'FastWeakTicket' using 'getFastWeakTicket' if the value hasn't been collected yet.
85+--
86+-- Synonymous with 'Weak'.
5787type FastWeak a = Weak a
5888#endif
5989
60−-- This needs to be in IO so we know that we've relinquished the ticket
90+-- |Return the @a@ kept alive by the given 'FastWeakTicket'.
91+--
92+-- This needs to be in IO so we know that we've relinquished the ticket.
6193getFastWeakTicketValue :: FastWeakTicket a -> IO a
6294#ifdef GHCJS_FAST_WEAK
6395getFastWeakTicketValue t = do
@@ -69,6 +101,7 @@ foreign import javascript unsafe "$r = $1.val;" js_ticketVal :: FastWeakTicket a
69101getFastWeakTicketValue = return . _fastWeakTicket_val
70102#endif
71103
104+-- |Get the value referred to by a 'FastWeak' if it hasn't yet been collected, or @Nothing@ if it has been collected.
72105getFastWeakValue :: FastWeak a -> IO (Maybe a)
73106#ifdef GHCJS_FAST_WEAK
74107getFastWeakValue w = do
@@ -84,6 +117,8 @@ foreign import javascript unsafe "$r = ($1.ticket === null) ? null : $1.ticket.v
84117getFastWeakValue = deRefWeak
85118#endif
86119
120+-- |Try to create a 'FastWeakTicket' for the given 'FastWeak' which will ensure the referred to value remains alive. Returns @Just@ if the value hasn't been
121+-- collected and a ticket can therefore be obtained, @Nothing@ if it's been collected.
87122getFastWeakTicket :: forall a. FastWeak a -> IO (Maybe (FastWeakTicket a))
88123#ifdef GHCJS_FAST_WEAK
89124getFastWeakTicket w = do
@@ -103,8 +138,10 @@ getFastWeakTicket w = do
103138 }
104139#endif
105140
106−-- I think it's fine if this is lazy - it'll retain the 'a', but so would the output; we just need to make sure it's forced before we start relying on the associated FastWeak to actually be weak
141+-- |Create a 'FastWeakTicket' directly from a value, creating a 'FastWeak' in the process which can be obtained with 'getFastWeakTicketValue'.
107142mkFastWeakTicket :: a -> IO (FastWeakTicket a)
143+-- I think it's fine if this is lazy - it'll retain the 'a', but so would the output; we just need to make sure it's forced before we start relying on the
144+-- associated FastWeak to actually be weak
108145#ifdef GHCJS_FAST_WEAK
109146mkFastWeakTicket v = js_fastWeakTicket (unsafeToRawJSVal v)
110147
@@ -119,11 +156,15 @@ mkFastWeakTicket v = do
119156 }
120157#endif
121158
159+-- |Demote a 'FastWeakTicket' which ensures the value is alive to a 'FastWeak' which doesn't. Note that unless the ticket or another for the same 'FastWeak' is
160+-- held some other way the value might be collected immediately.
161+getFastWeakTicketWeak :: FastWeakTicket a -> IO (FastWeak a)
122162-- Needs IO so that it can force the value - otherwise, could end up with a reference to the Ticket, which would retain the value
123163#ifdef GHCJS_FAST_WEAK
124−foreign import javascript unsafe "$r = $1.weak;" getFastWeakTicketWeak :: FastWeakTicket a -> IO (FastWeak a)
164+foreign import javascript unsafe "$r = $1.weak;" getFastWeakTicketWeak' :: FastWeakTicket a -> IO (FastWeak a)
165+{-# INLINE getFastWeakTicketWeak #-}
166+getFastWeakTicketWeak = getFastWeakTicketWeak'
125167#else
126−getFastWeakTicketWeak :: FastWeakTicket a -> IO (FastWeak a)
127168getFastWeakTicketWeak = return . _fastWeakTicket_weak
128169#endif
129170
src/Reflex/Patch/DMap.hs+16−8
@@ -5,6 +5,7 @@
55{-# LANGUAGE ScopedTypeVariables #-}
66{-# LANGUAGE StandaloneDeriving #-}
77{-# LANGUAGE TypeFamilies #-}
8+-- | 'Patch'es on 'DMap' that consist only of insertions (or overwrites) and deletions.
89module Reflex.Patch.DMap where
910
1011import Reflex.Patch.Class
@@ -20,14 +21,15 @@ import qualified Data.Map as Map
2021import Data.Semigroup (Semigroup (..))
2122import Data.Some (Some)
2223
23−-- | A set of changes to a 'DMap'. Any element may be inserted/updated or
24−-- deleted.
24+-- | A set of changes to a 'DMap'. Any element may be inserted/updated or deleted. Insertions are represented as @'ComposeMaybe' (Just value)@, while
25+-- deletions are represented as @'ComposeMaybe' Nothing@.
2526newtype PatchDMap k v = PatchDMap { unPatchDMap :: DMap k (ComposeMaybe v) }
2627
2728deriving instance GCompare k => Semigroup (PatchDMap k v)
2829
2930deriving instance GCompare k => Monoid (PatchDMap k v)
3031
32+-- |Apply the insertions or deletions to a given 'DMap'.
3133instance GCompare k => Patch (PatchDMap k v) where
3234 type PatchTarget (PatchDMap k v) = DMap k v
3335 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?
@@ -37,32 +39,38 @@ instance GCompare k => Patch (PatchDMap k v) where
3739 Nothing -> Just $ Constant ()
3840 Just _ -> Nothing
3941
42+-- | Map a function @v a -> v' a@ over any inserts/updates in the given @'PatchDMap' k v@ to produce a @'PatchDMap' k v'@.
4043mapPatchDMap :: (forall a. v a -> v' a) -> PatchDMap k v -> PatchDMap k v'
4144mapPatchDMap f (PatchDMap p) = PatchDMap $ DMap.map (ComposeMaybe . fmap f . getComposeMaybe) p
4245
43−traversePatchDMap :: Applicative m => (forall a. v a -> m (v' a)) -> PatchDMap k v -> m (PatchDMap k v')
46+-- | Map an effectful function @v a -> f (v' a)@ over any inserts/updates in the given @'PatchDMap' k v@ to produce a @'PatchDMap' k v'@.
47+traversePatchDMap :: Applicative f => (forall a. v a -> f (v' a)) -> PatchDMap k v -> f (PatchDMap k v')
4448traversePatchDMap f = traversePatchDMapWithKey $ const f
4549
50+-- | Map an effectful function @k a -> v a -> f (v' a)@ over any inserts/updates in the given @'PatchDMap' k v@ to produce a @'PatchDMap' k v'@.
4651traversePatchDMapWithKey :: Applicative m => (forall a. k a -> v a -> m (v' a)) -> PatchDMap k v -> m (PatchDMap k v')
4752traversePatchDMapWithKey f (PatchDMap p) = PatchDMap <$> DMap.traverseWithKey (\k (ComposeMaybe v) -> ComposeMaybe <$> traverse (f k) v) p
4853
54+-- | Weaken a @'PatchDMap' k v@ to a @'PatchMap' (Some k) v'@ using a function @v a -> v'@ to weaken each value contained in the patch.
4955weakenPatchDMapWith :: (forall a. v a -> v') -> PatchDMap k v -> PatchMap (Some k) v'
5056weakenPatchDMapWith f (PatchDMap p) = PatchMap $ weakenDMapWith (fmap f . getComposeMaybe) p
5157
52−patchDMapToPatchMapWith :: (f v -> v') -> PatchDMap (Const2 k v) f -> PatchMap k v'
58+-- | Convert a weak @'PatchDMap' ('Const2' k a) v@ where the @a@ is known by way of the @Const2@ into a @'PatchMap' k v'@ using a rank 1 function @v a -> v'@.
59+patchDMapToPatchMapWith :: (v a -> v') -> PatchDMap (Const2 k a) v -> PatchMap k v'
5360patchDMapToPatchMapWith f (PatchDMap p) = PatchMap $ dmapToMapWith (fmap f . getComposeMaybe) p
5461
55−const2PatchDMapWith :: forall k v f a. (v -> f a) -> PatchMap k v -> PatchDMap (Const2 k a) f
62+-- | Convert a @'PatchMap' k v@ into a @'PatchDMap' ('Const2' k a) v'@ using a function @v -> v' a@.
63+const2PatchDMapWith :: forall k v v' a. (v -> v' a) -> PatchMap k v -> PatchDMap (Const2 k a) v'
5664const2PatchDMapWith f (PatchMap p) = PatchDMap $ DMap.fromDistinctAscList $ g <$> Map.toAscList p
57− where g :: (k, Maybe v) -> DSum (Const2 k a) (ComposeMaybe f)
65+ where g :: (k, Maybe v) -> DSum (Const2 k a) (ComposeMaybe v')
5866 g (k, e) = Const2 k :=> ComposeMaybe (f <$> e)
5967
68+-- | Convert a @'PatchIntMap' v@ into a @'PatchDMap' ('Const2' Int a) v'@ using a function @v -> v' a@.
6069const2IntPatchDMapWith :: forall v f a. (v -> f a) -> PatchIntMap v -> PatchDMap (Const2 IntMap.Key a) f
6170const2IntPatchDMapWith f (PatchIntMap p) = PatchDMap $ DMap.fromDistinctAscList $ g <$> IntMap.toAscList p
6271 where g :: (IntMap.Key, Maybe v) -> DSum (Const2 IntMap.Key a) (ComposeMaybe f)
6372 g (k, e) = Const2 k :=> ComposeMaybe (f <$> e)
6473
65−-- | Get the values that will be deleted if the given patch is applied to the
66−-- given 'DMap'. Includes values that will be replaced.
74+-- | Get the values that will be replaced or deleted if the given patch is applied to the given 'DMap'.
6775getDeletions :: GCompare k => PatchDMap k v -> DMap k v' -> DMap k v'
6876getDeletions (PatchDMap p) m = DMap.intersectionWithKey (\_ v _ -> v) m p
src/Reflex/Patch/DMapWithMove.hs+3−2
@@ -121,6 +121,7 @@ validationErrorsForPatchDMapWithMove m =
121121 Just $ "unbalanced move at source key " <> gshow src <> " supposedly going to " <> gshow dst <> " but destination key is not moving"
122122 unbalancedMove _ = Nothing
123123
124+-- |Test whether two @'PatchDMapWithMove' k v@ contain the same patch operations.
124125instance EqTag k (NodeInfo k v) => Eq (PatchDMapWithMove k v) where
125126 PatchDMapWithMove a == PatchDMapWithMove b = a == b
126127
@@ -362,6 +363,7 @@ const2PatchDMapWithMoveWith f (PatchMapWithMove p) = PatchDMapWithMove $ DMap.fr
362363 , _nodeInfo_to = ComposeMaybe $ Const2 <$> MapWithMove._nodeInfo_to ni
363364 }
364365
366+-- | Apply the insertions, deletions, and moves to a given 'DMap'.
365367instance GCompare k => Patch (PatchDMapWithMove k v) where
366368 type PatchTarget (PatchDMapWithMove k v) = DMap k v
367369 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?
@@ -377,8 +379,7 @@ instance GCompare k => Patch (PatchDMapWithMove k v) where
377379 From_Delete -> Just $ Constant ()
378380 _ -> Nothing
379381
380−-- | Get the values that will be deleted or moved if the given patch is applied
381−-- to the given 'DMap'.
382+-- | Get the values that will be replaced, deleted, or moved if the given patch is applied to the given 'DMap'.
382383getDeletionsAndMoves :: GCompare k => PatchDMapWithMove k v -> DMap k v' -> DMap k (Product v' (ComposeMaybe k))
383384getDeletionsAndMoves (PatchDMapWithMove p) m = DMap.intersectionWithKey f m p
384385 where f _ v ni = Pair v $ _nodeInfo_to ni
src/Reflex/Patch/IntMap.hs+13−1
@@ -4,6 +4,7 @@
44{-# LANGUAGE DeriveTraversable #-}
55{-# LANGUAGE GeneralizedNewtypeDeriving #-}
66{-# LANGUAGE TypeFamilies #-}
7+-- |Module containing 'PatchIntMap', a 'Patch' for 'IntMap' which allows for insert/update or delete of associations.
78module Reflex.Patch.IntMap where
89
910import Prelude hiding (lookup)
@@ -13,8 +14,11 @@ import Data.Maybe
1314import Data.Semigroup
1415import Reflex.Patch.Class
1516
17+-- |'Patch' for 'IntMap' which represents insertion or deletion of keys in the mapping. Internally represented by 'IntMap (Maybe a)', where @Just@ means
18+-- insert/update and @Nothing@ means delete.
1619newtype PatchIntMap a = PatchIntMap (IntMap (Maybe a)) deriving (Functor, Foldable, Traversable, Monoid)
1720
21+-- |Apply the insertions or deletions to a given 'IntMap'.
1822instance Patch (PatchIntMap a) where
1923 type PatchTarget (PatchIntMap a) = IntMap a
2024 apply (PatchIntMap p) v = if IntMap.null p then Nothing else Just $
@@ -37,14 +41,22 @@ instance Semigroup (PatchIntMap v) where
3741 GT -> x
3842#endif
3943
40−traverseIntMapPatchWithKey :: Applicative t => (Int -> a -> t b) -> PatchIntMap a -> t (PatchIntMap b)
44+-- |Map a function @Int -> a -> b@ over all @a@s in the given @'PatchIntMap' a@ (that is, all inserts/updates), producing a @PatchIntMap b@.
45+mapIntMapPatchWithKey :: (Int -> a -> b) -> PatchIntMap a -> PatchIntMap b
46+mapIntMapPatchWithKey f (PatchIntMap m) = PatchIntMap $ IntMap.mapWithKey (\ k mv -> f k <$> mv) m
47+
48+-- |Map an effectful function @Int -> a -> f b@ over all @a@s in the given @'PatchIntMap' a@ (that is, all inserts/updates), producing a @f (PatchIntMap b)@.
49+traverseIntMapPatchWithKey :: Applicative f => (Int -> a -> f b) -> PatchIntMap a -> f (PatchIntMap b)
4150traverseIntMapPatchWithKey f (PatchIntMap m) = PatchIntMap <$> IntMap.traverseWithKey (\k mv -> traverse (f k) mv) m
4251
52+-- |Extract all @a@s inserted/updated by the given @'PatchIntMap' a@.
4353patchIntMapNewElements :: PatchIntMap a -> [a]
4454patchIntMapNewElements (PatchIntMap m) = catMaybes $ IntMap.elems m
4555
56+-- |Convert the given @'PatchIntMap' a@ into an @'IntMap' a@ with all the inserts/updates in the given patch.
4657patchIntMapNewElementsMap :: PatchIntMap a -> IntMap a
4758patchIntMapNewElementsMap (PatchIntMap m) = IntMap.mapMaybe id m
4859
60+-- |Subset the given @'IntMap' a@ to contain only the keys that would be deleted by the given @'PatchIntMap' a@.
4961getDeletions :: PatchIntMap v -> IntMap v' -> IntMap v'
5062getDeletions (PatchIntMap m) v = IntMap.intersection v m
src/Reflex/Patch/Map.hs+1−2
@@ -18,8 +18,7 @@ import Data.Maybe
1818newtype PatchMap k v = PatchMap { unPatchMap :: Map k (Maybe v) }
1919 deriving (Show, Read, Eq, Ord)
2020
21−-- | Applying a 'PatchMap' will update the 'Map' by performing the insertions
22−-- and deletions specified
21+-- |Apply the insertions or deletions to a given 'Map'.
2322instance Ord k => Patch (PatchMap k v) where
2423 type PatchTarget (PatchMap k v) = Map k v
2524 {-# INLINABLE apply #-}