-- | This module defines a way to prove that a key exists in a map, so that the
-- key can be used to index into the map without using a 'Maybe', or manually
-- handling the \"impossible\" case with 'error' or other partial functions.
--
-- To do this, @'Map' s k v@ has a type parameter @s@ that identifies its set of
-- keys, so that if another map has the same type parameter, you know that map
-- has the same set of keys. There is @'Key' s k@, a type of keys that have been
-- validated to belong to the set identified by @s@, and for which the operation
-- of indexing into a @'Map' s k v@ (only for the same @s@) can proceed without
-- failure (see '!'). The type @s@ itself has no internal structure, rather it
-- is merely a skolem type variable (rank-2 polymorphism
-- 'Control.Monad.ST.runST' trick) introduced by "Data.Reflection".
--
-- Like "Data.Map", functions in this module are strict in the keys but lazy in
-- the values. The "Data.Map.Strict.Refined" module reuses the same 'Map' type
-- but provides functions that operate strictly on the values.
--
-- = Warning
-- This module together with "Data.Map" rely on 'Eq' and 'Ord' instances being
-- lawful: that '==' is an equivalence relation, and that the 'Ord' operations
-- define a total order on the quotient defined by this equivalence relation; at
-- least for the subset of keys that are actually encountered at runtime. If
-- this assumption is violated, this module may not be able to uphold its
-- invariants and may throw errors. In particular beware of NaN in 'Float' and
-- 'Double'.
module Data.Map.Refined
(
-- * Map type
Common.Map
, Common.Key
-- * Existentials and common proofs
, Common.SomeMap(..)
, Common.withMap
, Common.SomeMapWith(..)
, Common.withMapWith
, Common.Some2MapWith(..)
, Common.with2MapWith
, SupersetProof(..)
, EmptyProof(..)
-- * Construction
, Common.empty
, singleton
, SingletonProof(..)
, fromSet
, Common.fromMap
, fromTraversableWithKey
, FromTraversableProof(..)
-- * Insertion
, insert
, InsertProof(..)
, reinsert
, insertLookupWithKey
-- * Deletion/Update
, Common.delete
, adjust
, adjustWithKey
, update
, updateLookupWithKey
-- * Query
, Common.lookup
, (Common.!)
, Common.member
, Common.lookupLT
, Common.lookupGT
, Common.lookupLE
, Common.lookupGE
, Common.null
, Common.isSubmapOfBy
, SubsetProof(..)
, Common.disjoint
, DisjointProof(..)
-- * Combine
, zipWithKey
, bind
, unionWithKey
, UnionProof(..)
, Common.difference
, DifferenceProof(..)
, differenceWithKey
, PartialDifferenceProof(..)
, intersectionWithKey
, IntersectionProof(..)
-- * Traversal
, mapWithKey
, traverseWithKey
, mapAccumLWithKey
, mapAccumRWithKey
, mapKeysWith
, MapProof(..)
, backpermuteKeys
-- * Folds
, Common.foldMapWithKey
, Common.foldrWithKey
, Common.foldlWithKey
, Common.foldrWithKey'
, Common.foldlWithKey'
-- * Conversion
, Common.toMap
, Common.keysSet
, Common.toList
, Common.toDescList
-- * Filter
, Common.restrictKeys
, Common.withoutKeys
, Common.filterWithKey
, Common.partitionWithKey
, PartitionProof(..)
, Common.spanAntitone
, PartialPartitionProof(..)
, mapMaybeWithKey
, mapEitherWithKey
, Common.splitLookup
, SplitProof(..)
-- * Min/Max
, updateMinWithKey
, updateMaxWithKey
, adjustMinWithKey
, adjustMaxWithKey
, Common.minViewWithKey
, Common.maxViewWithKey
-- * Casts
, Common.castKey
, Common.cast
, castFlavor
) where
import Data.Coerce
import Data.Container.Refined.Proofs
import Data.Container.Refined.Unsafe
import Data.Functor
import qualified Data.Map as Map
import Data.Map.Common.Refined
( Map(..), Key, unsafeCastKey, unsafeKey, SomeMapWith(..), Some2MapWith(..)
, fromSet, (!), zipWithKey, mapWithKey, traverseWithKey, bind
)
import qualified Data.Map.Common.Refined as Common
import Data.Traversable
import Data.Type.Coercion
import Prelude hiding (lookup, null)
import Refined
import Refined.Unsafe
-- | Create a map with a single key-value pair, and return a proof that the
-- key is in the resulting map.
singleton :: forall k a. k -> a -> SomeMapWith (SingletonProof 'Regular k) k a
singleton k v = SomeMapWith (Map $ Map.singleton k v)
$ SingletonProof (unsafeKey k)
-- | Create a map from an arbitrary traversable of key-value pairs.
fromTraversableWithKey
:: forall t k a. (Traversable t, Ord k)
=> (k -> a -> a -> a)
-> t (k, a)
-> SomeMapWith (FromTraversableProof 'Regular t k) k a
fromTraversableWithKey f xs = SomeMapWith (Map m) $ FromTraversableProof proof
where
(m, proof) = mapAccumL
(\s (k, v) -> let !s' = Map.insertWithKey f k v s in (s', unsafeKey k))
Map.empty
xs
-- | Insert a key-value pair into the map to obtain a potentially larger map,
-- guaranteed to contain the given key. If the key was already present, the
-- associated value is replaced with the supplied value.
insert
:: forall s k a. Ord k
=> k -> a -> Map s k a -> SomeMapWith (InsertProof 'Regular k s) k a
insert k v (Map m) = SomeMapWith (Map $ Map.insert k v m)
$ InsertProof (unsafeKey k) unsafeSubset
-- | Overwrite a key-value pair that is known to already be in the map. The set
-- of keys remains the same.
reinsert
:: forall s k a. Ord k
=> Key s k -> a -> Map s k a -> Map s k a
reinsert = gcoerceWith (unsafeCastKey @s @k) $ coerce $ Map.insert @k @a
-- | Insert a key-value pair into the map using a combining function, and if
-- the key was already present, the old value is returned along with the proof
-- that the key was present.
insertLookupWithKey
:: forall s k a. Ord k
=> (Key s k -> a -> a -> a)
-> k
-> a
-> Map s k a
-> (Maybe (Key s k, a), SomeMapWith (InsertProof 'Regular k s) k a)
insertLookupWithKey f k v (Map m)
= case Map.insertLookupWithKey (f . unsafeKey) k v m of
(v', !m') -> ((unsafeKey k,) <$> v',)
$ SomeMapWith (Map m') $ InsertProof (unsafeKey k) unsafeSubset
-- | Update the value at a specific key known the be in the map using the given
-- function. The set of keys remains the same.
adjust :: forall s k a. Ord k => (a -> a) -> Key s k -> Map s k a -> Map s k a
adjust = gcoerceWith (unsafeCastKey @s @k) $ coerce $ Map.adjust @k @a
-- | If the given key is in the map, update the associated value using the given
-- function with a proof that the key was in the map; otherwise return the map
-- unchanged. In any case the set of keys remains the same.
adjustWithKey
:: forall s k a. Ord k => (Key s k -> a -> a) -> k -> Map s k a -> Map s k a
adjustWithKey = gcoerceWith (unsafeCastKey @s @k) $ coerce
$ Map.adjustWithKey @k @a
-- | Update or delete a key known to be in the map using the given function,
-- returning a potentially smaller map.
update
:: forall s k a. Ord k
=> (a -> Maybe a)
-> Key s k
-> Map s k a
-> SomeMapWith (SupersetProof 'Regular s) k a
update f k (Map m) = SomeMapWith (Map $ Map.update f (unrefine k) m)
$ SupersetProof unsafeSubset
-- | If the given key is in the map, update or delete it using the given
-- function with a proof that the key was in the map; otherwise the map is
-- unchanged. Alongside return the new value if it was updated, or the old value
-- if it was deleted, and a proof that the key was in the map.
updateLookupWithKey
:: forall s k a. Ord k
=> (Key s k -> a -> Maybe a)
-> k
-> Map s k a
-> (Maybe (Key s k, a), SomeMapWith (SupersetProof 'Regular s) k a)
updateLookupWithKey f k (Map m)
= case Map.updateLookupWithKey (f . unsafeKey) k m of
(v', !m') -> ((unsafeKey k,) <$> v',)
$ SomeMapWith (Map m') $ SupersetProof unsafeSubset
-- | Return the union of two maps, with a given combining function for keys that
-- exist in both maps simultaneously.
--
-- You can use 'andLeft' and 'andRight' to obtain @'Key' s k@ and @'Key' t k@
-- respectively.
unionWithKey
:: forall s t k a. Ord k
=> (Refined (InSet 'Regular s && InSet 'Regular t) k -> a -> a -> a)
-> Map s k a
-> Map t k a
-> SomeMapWith (UnionProof 'Regular s t) k a
unionWithKey f (Map m1) (Map m2)
= SomeMapWith (Map $ Map.unionWithKey (f . reallyUnsafeRefine) m1 m2)
$ UnionProof unsafeSubset unsafeSubsetWith2
-- | For keys that appear in both maps, the given function decides whether the
-- key is removed from the first map.
--
-- You can use 'andLeft' and 'andRight' to obtain @'Key' s k@ and @'Key' t k@
-- respectively.
differenceWithKey
:: forall s t k a b. Ord k
=> (Refined (InSet 'Regular s && InSet 'Regular t) k -> a -> b -> Maybe a)
-> Map s k a
-> Map t k b
-> SomeMapWith (PartialDifferenceProof 'Regular s t) k a
differenceWithKey f (Map m1) (Map m2)
= SomeMapWith (Map $ Map.differenceWithKey (f . reallyUnsafeRefine) m1 m2)
$ PartialDifferenceProof unsafeSubset unsafeSubset
-- | Return the intersection of two maps with the given combining function.
--
-- You can use 'andLeft' and 'andRight' to obtain @'Key' s k@ and @'Key' t k@
-- respectively.
intersectionWithKey
:: forall s t k a b c. Ord k
=> (Refined (InSet 'Regular s && InSet 'Regular t) k -> a -> b -> c)
-> Map s k a
-> Map t k b
-> SomeMapWith (IntersectionProof 'Regular s t) k c
intersectionWithKey f (Map m1) (Map m2)
= SomeMapWith (Map $ Map.intersectionWithKey (f . reallyUnsafeRefine) m1 m2)
$ IntersectionProof unsafeSubset unsafeSubsetWith2
-- | Thread an accumularing argument through the map in ascending order of keys.
mapAccumLWithKey
:: forall s k a b c. (a -> Key s k -> b -> (a, c))
-> a
-> Map s k b
-> (a, Map s k c)
mapAccumLWithKey = gcoerceWith (unsafeCastKey @s @k) $ coerce
$ Map.mapAccumWithKey @a @k @b @c
-- | Thread an accumularing argument through the map in descending order of
-- keys.
mapAccumRWithKey
:: forall s k a b c. (a -> Key s k -> b -> (a, c))
-> a
-> Map s k b
-> (a, Map s k c)
mapAccumRWithKey = gcoerceWith (unsafeCastKey @s @k) $ coerce
$ Map.mapAccumRWithKey @a @k @b @c
-- | @'mapKeysWith' c f m@ applies @f@ to each key of @m@ and collects the
-- results into a new map. For keys that were mapped to the same new key, @c@
-- acts as the combining function for corresponding values.
mapKeysWith
:: forall s k1 k2 a. Ord k2
=> (a -> a -> a)
-> (Key s k1 -> k2)
-> Map s k1 a
-> SomeMapWith (MapProof 'Regular s k1 k2) k2 a
mapKeysWith f g (Map m)
= SomeMapWith (Map $ Map.mapKeysWith f (g . unsafeKey) m)
$ MapProof (unsafeKey . g) \k2 -> case Map.lookup (unrefine k2) backMap of
Nothing -> error "mapKeysWith: bug: Data.Map.Refined has been subverted"
Just k1 -> k1
where
~backMap = Map.fromList
[ (k2, unsafeKey k1)
| k1 <- Map.keys m
, let !k2 = g $ unsafeKey k1
]
-- | Apply a function to all values in a map, together with their corresponding
-- keys, and collect only the 'Just' results, returning a potentially smaller
-- map.
mapMaybeWithKey
:: forall s k a b. (Key s k -> a -> Maybe b)
-> Map s k a
-> SomeMapWith (SupersetProof 'Regular s) k b
mapMaybeWithKey f (Map m)
= SomeMapWith (Map $ Map.mapMaybeWithKey (f . unsafeKey) m)
$ SupersetProof unsafeSubset
-- | Apply a function to all values in a map, together with their corresponding
-- keys, and collect the 'Left' and 'Right' results into separate (disjoint)
-- maps.
mapEitherWithKey
:: forall s k a b c. Ord k -- TODO: this is only used in the proof
=> (Key s k -> a -> Either b c)
-> Map s k a
-> Some2MapWith (PartitionProof 'Regular s k) k b c
mapEitherWithKey p (Map m) = case Map.mapEitherWithKey (p . unsafeKey) m of
(m1, m2) -> Some2MapWith (Map m1) (Map m2) $ PartitionProof
do \k -> case Map.lookup (unrefine k) m of
Nothing
-> error "mapEitherWithKey: bug: Data.Map.Refined has been subverted"
Just x -> case p k x of
Left _ -> Left $ unsafeKey $ unrefine k
Right _ -> Right $ unsafeKey $ unrefine k
unsafeSubset unsafeSubsetWith2 \f g -> unsafeSubsetWith2 f g
-- | Update or delete the value at the smallest key, returning a potentially
-- smaller map.
updateMinWithKey
:: forall s k a. (Key s k -> a -> Maybe a)
-> Map s k a
-> SomeMapWith (SupersetProof 'Regular s) k a
updateMinWithKey f (Map m)
= SomeMapWith (Map $ Map.updateMinWithKey (f . unsafeKey) m)
$ SupersetProof unsafeSubset
-- | Update or delete the value at the largest key, returning a potentially
-- smaller map.
updateMaxWithKey
:: forall s k a. (Key s k -> a -> Maybe a)
-> Map s k a
-> SomeMapWith (SupersetProof 'Regular s) k a
updateMaxWithKey f (Map m)
= SomeMapWith (Map $ Map.updateMaxWithKey (f . unsafeKey) m)
$ SupersetProof unsafeSubset
-- | Adjust the value at the smallest key. The set of keys remains unchanged.
adjustMinWithKey :: forall s k a. (Key s k -> a -> a) -> Map s k a -> Map s k a
adjustMinWithKey f (Map m)
= Map $ Map.updateMinWithKey ((Just .) . f . unsafeKey) m
-- | Adjust the value at the greatest key. The set of keys remains unchanged.
adjustMaxWithKey :: forall s k a. (Key s k -> a -> a) -> Map s k a -> Map s k a
adjustMaxWithKey f (Map m)
= Map $ Map.updateMaxWithKey ((Just .) . f . unsafeKey) m
-- | Apply the inverse image of the given function to the keys of the given map,
-- so that for all @k :: 'Key' s2 k2@,
-- @'backpermuteKeys' f m '!' k = m '!' f k@.
--
-- If maps are identified with functions, this computes the composition.
backpermuteKeys
:: forall s1 s2 k1 k2 a. (Ord k1, KnownSet s2 k2)
=> (Key s2 k2 -> Key s1 k1) -> Map s1 k1 a -> Map s2 k2 a
backpermuteKeys f m = fromSet \k -> m ! f k